tr]:last:border-b-0', className)}
38 | {...props}
39 | />
40 | );
41 | }
42 |
43 | function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
44 | return (
45 |
53 | );
54 | }
55 |
56 | function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
57 | return (
58 | [role=checkbox]]:translate-y-[2px]',
62 | className,
63 | )}
64 | {...props}
65 | />
66 | );
67 | }
68 |
69 | function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
70 | return (
71 | [role=checkbox]]:translate-y-[2px]',
75 | className,
76 | )}
77 | {...props}
78 | />
79 | );
80 | }
81 |
82 | function TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {
83 | return (
84 |
89 | );
90 | }
91 |
92 | export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
93 |
--------------------------------------------------------------------------------
/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as TabsPrimitive from '@radix-ui/react-tabs';
5 |
6 | import { cn } from '@/lib/utils';
7 |
8 | function Tabs({ className, ...props }: React.ComponentProps) {
9 | return (
10 |
15 | );
16 | }
17 |
18 | function TabsList({ className, ...props }: React.ComponentProps) {
19 | return (
20 |
28 | );
29 | }
30 |
31 | function TabsTrigger({ className, ...props }: React.ComponentProps) {
32 | return (
33 |
41 | );
42 | }
43 |
44 | function TabsContent({ className, ...props }: React.ComponentProps) {
45 | return (
46 |
51 | );
52 | }
53 |
54 | export { Tabs, TabsList, TabsTrigger, TabsContent };
55 |
--------------------------------------------------------------------------------
/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/utils';
4 |
5 | function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
6 | return (
7 |
15 | );
16 | }
17 |
18 | export { Textarea };
19 |
--------------------------------------------------------------------------------
/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
5 | import { type VariantProps } from 'class-variance-authority';
6 |
7 | import { cn } from '@/lib/utils';
8 | import { toggleVariants } from '@/components/ui/toggle';
9 |
10 | const ToggleGroupContext = React.createContext>({
11 | size: 'default',
12 | variant: 'default',
13 | });
14 |
15 | function ToggleGroup({
16 | className,
17 | variant,
18 | size,
19 | children,
20 | ...props
21 | }: React.ComponentProps & VariantProps) {
22 | return (
23 |
33 |
34 | {children}
35 |
36 |
37 | );
38 | }
39 |
40 | function ToggleGroupItem({
41 | className,
42 | children,
43 | variant,
44 | size,
45 | ...props
46 | }: React.ComponentProps & VariantProps) {
47 | const context = React.useContext(ToggleGroupContext);
48 |
49 | return (
50 |
64 | {children}
65 |
66 | );
67 | }
68 |
69 | export { ToggleGroup, ToggleGroupItem };
70 |
--------------------------------------------------------------------------------
/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as TogglePrimitive from '@radix-ui/react-toggle';
5 | import { cva, type VariantProps } from 'class-variance-authority';
6 |
7 | import { cn } from '@/lib/utils';
8 |
9 | const toggleVariants = cva(
10 | "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
11 | {
12 | variants: {
13 | variant: {
14 | default: 'bg-transparent',
15 | outline:
16 | 'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground',
17 | },
18 | size: {
19 | default: 'h-9 px-2 min-w-9',
20 | sm: 'h-8 px-1.5 min-w-8',
21 | lg: 'h-10 px-2.5 min-w-10',
22 | },
23 | },
24 | defaultVariants: {
25 | variant: 'default',
26 | size: 'default',
27 | },
28 | },
29 | );
30 |
31 | function Toggle({
32 | className,
33 | variant,
34 | size,
35 | ...props
36 | }: React.ComponentProps & VariantProps) {
37 | return (
38 |
43 | );
44 | }
45 |
46 | export { Toggle, toggleVariants };
47 |
--------------------------------------------------------------------------------
/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as TooltipPrimitive from '@radix-ui/react-tooltip';
5 |
6 | import { cn } from '@/lib/utils';
7 |
8 | function TooltipProvider({
9 | delayDuration = 0,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
18 | );
19 | }
20 |
21 | function Tooltip({ ...props }: React.ComponentProps) {
22 | return (
23 |
24 |
25 |
26 | );
27 | }
28 |
29 | function TooltipTrigger({ ...props }: React.ComponentProps) {
30 | return ;
31 | }
32 |
33 | function TooltipContent({
34 | className,
35 | sideOffset = 0,
36 | children,
37 | ...props
38 | }: React.ComponentProps) {
39 | return (
40 |
41 |
50 | {children}
51 |
52 |
53 |
54 | );
55 | }
56 |
57 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
58 |
--------------------------------------------------------------------------------
/config/mobile.ts:
--------------------------------------------------------------------------------
1 | import { MainNavItem, SidebarNavItem } from '@/types';
2 |
3 | interface MobileConfig {
4 | mainNav: MainNavItem[];
5 | sidebarNav: SidebarNavItem[];
6 | }
7 |
8 | export const mobileConfig: MobileConfig = {
9 | mainNav: [
10 | {
11 | title: 'Home',
12 | href: '/',
13 | },
14 | {
15 | title: 'Anime',
16 | href: '/anime',
17 | },
18 | {
19 | title: 'Drama',
20 | href: '/drama',
21 | },
22 | {
23 | title: 'Movie',
24 | href: '/movie',
25 | },
26 | {
27 | title: 'TV',
28 | href: '/tv',
29 | },
30 | ],
31 | sidebarNav: [
32 | {
33 | title: 'Other Features',
34 | items: [
35 | {
36 | title: 'List',
37 | href: '/list',
38 | items: [],
39 | },
40 | ],
41 | },
42 | ],
43 | };
44 |
--------------------------------------------------------------------------------
/config/site.ts:
--------------------------------------------------------------------------------
1 | export type SiteConfig = typeof siteConfig;
2 |
3 | export const siteConfig = {
4 | name: 'EnjoyTown',
5 | description:
6 | 'Beautifully designed website where you can watch anime, drama, movies and tv shows for free. Built with Next.JS and shadcn/ui.',
7 | mainNav: [
8 | {
9 | title: 'Movie',
10 | href: '/movie',
11 | },
12 | {
13 | title: 'Anime',
14 | href: '/anime',
15 | },
16 | {
17 | title: 'TV',
18 | href: '/tv',
19 | },
20 | ],
21 | links: {
22 | twitter: 'https://twitter.com/avalynndev',
23 | github: 'https://github.com/avalynndev/enjoytown',
24 | enjoytown: '/',
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/db/drizzle.ts:
--------------------------------------------------------------------------------
1 | import { neon } from '@neondatabase/serverless';
2 | import { drizzle } from 'drizzle-orm/neon-http';
3 |
4 | const sql = neon(process.env.DATABASE_URL!);
5 | export const db = drizzle({ client: sql });
6 |
--------------------------------------------------------------------------------
/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import 'dotenv/config';
2 | import { defineConfig } from 'drizzle-kit';
3 |
4 | export default defineConfig({
5 | out: './drizzle',
6 | schema: './schema',
7 | dialect: 'postgresql',
8 | dbCredentials: {
9 | url: process.env.DATABASE_URL!,
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/env.mjs:
--------------------------------------------------------------------------------
1 | import { createEnv } from '@t3-oss/env-nextjs';
2 | import { z } from 'zod';
3 |
4 | export const env = createEnv({
5 | shared: {
6 | DOWNLOAD_API_URL: z.string().url(),
7 | CONSUMET_API_URL: z.string().url(),
8 | TMDB_API_KEY: z.string(),
9 | NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
10 | },
11 | experimental__runtimeEnv: {
12 | NODE_ENV: process.env.NODE_ENV,
13 | NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
14 | },
15 | skipValidation: !!process.env.SKIP_ENV_VALIDATION,
16 | emptyStringAsUndefined: true,
17 | });
18 |
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | DOWNLOAD_API_URL=https://v1.api.ani.rohi.dev/api/dramacool
2 | CONSUMET_API_URL=https://consumet_api_url
3 | TMDB_API_KEY=
4 | # Find how to get your own api key on the readme
5 | PROXY_M3U8 = "https://yourproxy.com/fetch?url=
6 | # PROXY_M3U8 would be found at https://github.com/JulzOhern/Gogoanime-and-Hianime-proxy. If deploying to vercel keep . as output directory.
--------------------------------------------------------------------------------
/hooks/use-mobile.ts:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import * as React from 'react';
3 |
4 | const MOBILE_BREAKPOINT = 1024;
5 |
6 | export function useIsMobile() {
7 | const [isMobile, setIsMobile] = React.useState(() => {
8 | if (typeof window !== 'undefined') {
9 | return window.innerWidth < MOBILE_BREAKPOINT;
10 | }
11 | return false;
12 | });
13 |
14 | React.useEffect(() => {
15 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
16 | const onChange = () => {
17 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
18 | };
19 | mql.addEventListener('change', onChange);
20 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
21 | return () => mql.removeEventListener('change', onChange);
22 | }, []);
23 |
24 | return isMobile;
25 | }
26 |
--------------------------------------------------------------------------------
/hooks/use-search.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { tmdb } from '@/lib/tmdb';
3 | import { fetchAnimeSearch } from '@/lib/consumet';
4 |
5 | type SearchCategory = 'movie' | 'tv' | 'anime';
6 |
7 | export function useSearch(search: string, category: SearchCategory) {
8 | const [isLoading, setIsLoading] = useState(false);
9 | const [results, setResults] = useState(null);
10 |
11 | useEffect(() => {
12 | if (!search.trim()) {
13 | setResults(null);
14 | return;
15 | }
16 |
17 | const delayDebounce = setTimeout(async () => {
18 | setIsLoading(true);
19 |
20 | if (category === 'movie') {
21 | const movieData = await tmdb.movies.search(search, 'en-US');
22 | setResults(movieData.results);
23 | } else if (category === 'tv') {
24 | const tvData = await tmdb.tv.search(search, 'en-US');
25 | setResults(tvData.results);
26 | } else if (category === 'anime') {
27 | const animeData = await fetchAnimeSearch(search);
28 | setResults(animeData.results);
29 | }
30 |
31 | setIsLoading(false);
32 | }, 500);
33 |
34 | return () => clearTimeout(delayDebounce);
35 | }, [search, category]);
36 |
37 | return { results, isLoading };
38 | }
39 |
--------------------------------------------------------------------------------
/hooks/use-session.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/hooks/use-session.ts
--------------------------------------------------------------------------------
/lib/auth-client.ts:
--------------------------------------------------------------------------------
1 | import { createAuthClient } from 'better-auth/react';
2 | import { usernameClient, multiSessionClient, magicLinkClient } from 'better-auth/client/plugins';
3 |
4 | export const authClient = createAuthClient({
5 | baseURL: process.env.NEXT_PUBLIC_APP_URL,
6 | plugins: [usernameClient(), multiSessionClient(), magicLinkClient()],
7 | });
8 |
9 | export const { signIn, signOut, signUp, useSession } = authClient;
10 |
--------------------------------------------------------------------------------
/lib/auth.ts:
--------------------------------------------------------------------------------
1 | import { betterAuth } from 'better-auth';
2 | import { drizzleAdapter } from 'better-auth/adapters/drizzle';
3 | import { username, multiSession, magicLink } from 'better-auth/plugins';
4 | import { db } from '@/db/drizzle';
5 | import { schema } from '@/schema';
6 | import { nextCookies } from 'better-auth/next-js';
7 | import { Resend } from 'resend';
8 | import { EmailTemplate } from '@daveyplate/better-auth-ui/server';
9 |
10 | const resend = new Resend(process.env.RESEND_API_KEY || '');
11 | const fromEmail = 'AVALYNNDEV '; // add email
12 |
13 | export const auth = betterAuth({
14 | appName: 'Enjoytown',
15 | emailAndPassword: {
16 | enabled: true,
17 | sendResetPassword: async ({ user, url, token }) => {
18 | await resend.emails.send({
19 | from: fromEmail,
20 | to: user.email,
21 | subject: 'Reset your password',
22 | react: EmailTemplate({
23 | heading: 'Reset Password',
24 | content: 'Click the button below to reset your password.',
25 | action: 'Reset Password',
26 | url,
27 | }),
28 | });
29 | },
30 | },
31 | database: drizzleAdapter(db, {
32 | provider: 'pg',
33 | schema: schema,
34 | }),
35 | user: {
36 | changeEmail: {
37 | enabled: true,
38 | sendChangeEmailVerification: async ({ user, newEmail, url, token }, request) => {
39 | await resend.emails.send({
40 | from: fromEmail,
41 | to: user.email,
42 | subject: 'Verify your email change',
43 | react: EmailTemplate({
44 | heading: 'Verify Email Change',
45 | content: 'Click the button below to verify your email change.',
46 | action: 'Verify Email Change',
47 | url,
48 | }),
49 | });
50 | },
51 | },
52 | deleteUser: {
53 | enabled: true,
54 | },
55 | },
56 | plugins: [
57 | nextCookies(),
58 | username(),
59 | multiSession(),
60 | magicLink({
61 | sendMagicLink: async ({ email, token, url }, request) => {
62 | await resend.emails.send({
63 | from: fromEmail,
64 | to: email,
65 | subject: 'Magic Link',
66 | react: EmailTemplate({
67 | heading: 'Magic Link',
68 | content: 'Click the button below to securely log in.',
69 | action: 'Log In',
70 | url: url,
71 | }),
72 | });
73 | },
74 | }),
75 | ],
76 | });
77 | export const { getSession } = auth.api;
78 |
79 | export type SessionData = (typeof auth)['$Infer']['Session'];
80 |
--------------------------------------------------------------------------------
/lib/consumet/index.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 | export async function FetchAnimeInfo(data: any) {
3 | try {
4 | const fetchPromises = data.results.map(async (element: any) => {
5 | const link = `${process.env.CONSUMET_API_URL}/movies/dramacool/info?id=${element.id}`;
6 | await fetch(link, { next: { revalidate: 21600 } });
7 | });
8 |
9 | await Promise.all(fetchPromises);
10 | } catch (error) {
11 | console.error('Error occurred while pre-fetching video links:', error);
12 | }
13 | }
14 |
15 | export async function fetchAnimeSearch(text: any) {
16 | const res = await fetch(`${process.env.CONSUMET_API_URL}/meta/anilist/${text}`, {
17 | next: { revalidate: 21600 },
18 | });
19 | const data = await res.json();
20 | return data;
21 | }
22 |
23 | export async function fetchDramaSearch(title: any) {
24 | const res = await fetch(`${process.env.CONSUMET_API_URL}/movies/dramacool/${title}`, {
25 | cache: 'force-cache',
26 | });
27 | const data = await res.json();
28 | return data;
29 | }
30 |
31 | export async function getVideoLink(epiId: any, mediaId: any) {
32 | let videoLink;
33 | const res = await fetch(
34 | `${process.env.CONSUMET_API_URL}/movies/dramacool/watch?episodeId=${epiId}&mediaId=drama-detail/${mediaId}`,
35 | { cache: 'force-cache' },
36 | );
37 | const data = await res.json();
38 | videoLink = data.sources[0].url;
39 | return videoLink;
40 | }
41 |
42 | export async function PreFetchMangaInfo(data: any) {
43 | try {
44 | const fetchPromises = data.results.map(async (element: any) => {
45 | const link = `${process.env.CONSUMET_API_URL}/meta/anilist-manga/${element.id}?provider=mangareader`;
46 | await fetch(link, { next: { revalidate: 86400 } });
47 | });
48 | await Promise.all(fetchPromises);
49 | } catch (error) {
50 | console.error('error', error);
51 | }
52 | }
53 |
54 | export async function PreFetchChaterLinks(data: any) {
55 | try {
56 | const fetchPromises = data.map(async (element: any) => {
57 | const link = `${process.env.CONSUMET_API_URL}/meta/anilist-manga/read?chapterId=${element.id}&provider=mangadex`;
58 | await fetch(link, { cache: 'force-cache' });
59 | });
60 |
61 | await Promise.all(fetchPromises);
62 | console.log('Chapter links pre-fetched successfully!');
63 | } catch (error) {
64 | console.error('Error occurred while pre-fetching chapter links:', error);
65 | }
66 | }
67 |
68 | export async function GetSearchedAnime(title: any) {
69 | const res = await fetch(
70 | `${process.env.CONSUMET_API_URL}/meta/anilist-manga/` + title + '?provider=mangareader',
71 | );
72 | const data = await res.json();
73 | return data;
74 | }
75 |
76 | export async function getMangaInfo(id: any) {
77 | const res = await fetch(
78 | `${process.env.CONSUMET_API_URL}/meta/anilist-manga/info/${id}?provider=mangareader`,
79 | { next: { revalidate: 21600 } },
80 | );
81 | const data = await res.json();
82 | return data;
83 | }
84 |
85 | export async function fetchChapter(id: any) {
86 | const res = await fetch(
87 | `${process.env.CONSUMET_API_URL}/meta/anilist-manga/read?chapterId=${id}&provider=mangareader`,
88 | { next: { revalidate: 21600 } },
89 | );
90 | const data = await res.json();
91 | return data;
92 | }
93 |
--------------------------------------------------------------------------------
/lib/download.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { env } from '@/env.mjs';
4 |
5 | export async function getDramaDownload(episode: any) {
6 | const res = await fetch(`${env.DOWNLOAD_API_URL}/episode/${episode}`, {
7 | next: { revalidate: 21600 },
8 | });
9 | const data = await res.json();
10 | return data;
11 | }
12 |
--------------------------------------------------------------------------------
/lib/localstorage.ts:
--------------------------------------------------------------------------------
1 | import { data_types } from '@/types';
2 |
3 | const SaveToLocalStorage = (data: data_types) => {
4 | try {
5 | const jsonData = localStorage.getItem('watchHistory');
6 | const dataObject = jsonData ? JSON.parse(jsonData) : {};
7 |
8 | if (!dataObject.AnimeHistory) {
9 | dataObject.AnimeHistory = [];
10 | }
11 |
12 | let found_anime = false;
13 | if (data.type === 'anime') {
14 | dataObject.AnimeHistory.forEach((element: data_types) => {
15 | if (element.title === data.title) {
16 | element.episode_number = data.episode_number;
17 | found_anime = true;
18 | }
19 | });
20 | }
21 |
22 | if (!found_anime) {
23 | dataObject.AnimeHistory.push(data);
24 | }
25 |
26 | let updatedData = JSON.stringify(dataObject);
27 | localStorage.setItem('watchHistory', updatedData);
28 | } catch (error) {
29 | //('Some error occured while saving the data. Please contact the DEVs.');
30 | }
31 | };
32 |
33 | export default SaveToLocalStorage;
34 |
--------------------------------------------------------------------------------
/lib/store.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand';
2 |
3 | interface ToastStore {
4 | showToast: boolean;
5 | triggerToast: () => void;
6 | }
7 |
8 | export const useToastStore = create((set) => ({
9 | showToast: true, // Always show toast
10 | triggerToast: () => set({ showToast: true }),
11 | }));
12 |
--------------------------------------------------------------------------------
/lib/tmdb/api/collections.ts:
--------------------------------------------------------------------------------
1 | import { axiosClient } from '@/lib/tmdb/index';
2 | import { Language } from '@/lib/tmdb/models/language';
3 |
4 | const details = async (id: number, language: Language) => {
5 | const { data } = await axiosClient.get(`/collection/${id}`, {
6 | params: {
7 | language,
8 | },
9 | });
10 |
11 | return data;
12 | };
13 |
14 | export const collections = { details };
15 |
--------------------------------------------------------------------------------
/lib/tmdb/api/credits.ts:
--------------------------------------------------------------------------------
1 | import { axiosClient, Credits, Language } from '@/lib/tmdb';
2 |
3 | export const credits = async (variant: 'movie' | 'tv', id: number, language: Language) => {
4 | const { data } = await axiosClient.get(`/${variant}/${id}/credits`, {
5 | params: {
6 | language,
7 | },
8 | });
9 |
10 | return data;
11 | };
12 |
--------------------------------------------------------------------------------
/lib/tmdb/api/genres.ts:
--------------------------------------------------------------------------------
1 | import { axiosClient, GetGenresResponse, Language } from '@/lib/tmdb';
2 |
3 | export const genres = async (type: 'movie' | 'tv', language: Language) => {
4 | const { data } = await axiosClient.get(`/genre/${type}/list`, {
5 | params: {
6 | language,
7 | },
8 | });
9 |
10 | return data;
11 | };
12 |
--------------------------------------------------------------------------------
/lib/tmdb/api/images.ts:
--------------------------------------------------------------------------------
1 | import { axiosClient, GetImagesResponse } from '@/lib/tmdb';
2 |
3 | export const images = async (variant: 'movie' | 'tv' | 'person', id: number) => {
4 | const { data } = await axiosClient.get(`/${variant}/${id}/images`);
5 |
6 | return data;
7 | };
8 |
--------------------------------------------------------------------------------
/lib/tmdb/api/index.ts:
--------------------------------------------------------------------------------
1 | export * from './collections';
2 | export * from './credits';
3 | export * from './genres';
4 | export * from './images';
5 | export * from './keywords';
6 | export * from './languages';
7 | export * from './movies';
8 | export * from './person';
9 | export * from './search';
10 | export * from './season';
11 | export * from './tv-series';
12 | export * from './videos';
13 | export * from './watch-providers';
14 |
--------------------------------------------------------------------------------
/lib/tmdb/api/keywords.ts:
--------------------------------------------------------------------------------
1 | import { axiosClient, GetKeywordsResponse } from '@/lib/tmdb';
2 |
3 | export const keywords = async (type: 'tv' | 'movie', id: number) => {
4 | const { data } = await axiosClient.get(`/${type}/${id}/keywords`);
5 |
6 | return data.keywords || data.results;
7 | };
8 |
--------------------------------------------------------------------------------
/lib/tmdb/api/languages.ts:
--------------------------------------------------------------------------------
1 | import { axiosClient } from '..';
2 | import { GetLanguagesResponse } from '@/lib/tmdb';
3 |
4 | export const languages = async () => {
5 | const { data } = await axiosClient.get('/configuration/languages');
6 |
7 | return data;
8 | };
9 |
--------------------------------------------------------------------------------
/lib/tmdb/api/person.ts:
--------------------------------------------------------------------------------
1 | import { Language, PersonDetails, PersonWithMediaType, axiosClient } from '..';
2 | import { CombinedCredits, CombinedCreditsResponse } from '@/lib/tmdb';
3 | import { formatCombinedCredit } from '@/lib/tmdb/utils/format-combined-credit';
4 | import { ListResponse } from '@/lib/tmdb/utils/list-response';
5 |
6 | /*
7 | |-----------------------------------------------------------------------------
8 | | Popular
9 | |
10 | | References:
11 | | https://developer.themoviedb.org/reference/person-popular-list
12 | |
13 | |-----------------------------------------------------------------------------
14 | */
15 |
16 | type PopularPeopleQueryParams = {
17 | language: Language;
18 | page: number;
19 | };
20 |
21 | const popular = async (queryParams: PopularPeopleQueryParams) => {
22 | const { data } = await axiosClient.get>('/person/popular', {
23 | params: {
24 | ...queryParams,
25 | },
26 | });
27 |
28 | return data;
29 | };
30 |
31 | /*
32 | |-----------------------------------------------------------------------------
33 | | Details
34 | |
35 | | References:
36 | | https://developer.themoviedb.org/reference/person-details
37 | |
38 | |-----------------------------------------------------------------------------
39 | */
40 |
41 | const details = async (personId: number, language: Language) => {
42 | const { data } = await axiosClient.get(`/person/${personId}`, {
43 | params: {
44 | language,
45 | },
46 | });
47 |
48 | return data;
49 | };
50 |
51 | /*
52 | |-----------------------------------------------------------------------------
53 | | Combined credits
54 | |
55 | | References:
56 | | https://developer.themoviedb.org/reference/person-details
57 | |
58 | |-----------------------------------------------------------------------------
59 | */
60 |
61 | const combinedCredits = async (personId: number, language: Language) => {
62 | const { data } = await axiosClient.get(
63 | `/person/${personId}/combined_credits`,
64 | {
65 | params: {
66 | language,
67 | },
68 | },
69 | );
70 |
71 | const formattedResponse: CombinedCredits = {
72 | cast: data.cast.map((credit) => formatCombinedCredit(credit)),
73 | crew: data.cast.map((credit) => formatCombinedCredit(credit)),
74 | };
75 |
76 | return formattedResponse;
77 | };
78 |
79 | export const person = { popular, details, combinedCredits };
80 |
--------------------------------------------------------------------------------
/lib/tmdb/api/search.ts:
--------------------------------------------------------------------------------
1 | import { axiosClient } from '..';
2 |
3 | import {
4 | Language,
5 | MovieWithMediaType,
6 | TvSerieWithMediaType,
7 | PersonWithMediaType,
8 | } from '@/lib/tmdb';
9 | import { ListResponse } from '@/lib/tmdb/utils/list-response';
10 |
11 | const multi = async (query: string, language: Language) => {
12 | const { data } = await axiosClient.get<
13 | ListResponse
14 | >('/search/multi', {
15 | params: {
16 | query,
17 | language,
18 | },
19 | });
20 |
21 | return data;
22 | };
23 |
24 | export const search = { multi };
25 |
--------------------------------------------------------------------------------
/lib/tmdb/api/season.ts:
--------------------------------------------------------------------------------
1 | import { axiosClient } from '..';
2 | import { Language } from '@/lib/tmdb';
3 | import { SeasonDetails } from '@/lib/tmdb';
4 |
5 | const details = async (seriesId: number, seasonNumber: number, language: Language) => {
6 | const { data } = await axiosClient.get(`/tv/${seriesId}/season/${seasonNumber}`, {
7 | params: {
8 | language,
9 | },
10 | });
11 |
12 | return data;
13 | };
14 |
15 | export const season = { details };
16 |
--------------------------------------------------------------------------------
/lib/tmdb/api/videos.ts:
--------------------------------------------------------------------------------
1 | import { axiosClient } from '..';
2 | import { GetVideosResponse } from '@/lib/tmdb';
3 |
4 | type Variant = 'movie' | 'tv';
5 |
6 | export const videos = async (variant: Variant, id: number) => {
7 | const { data } = await axiosClient.get(`/${variant}/${id}/videos`);
8 |
9 | return data;
10 | };
11 |
--------------------------------------------------------------------------------
/lib/tmdb/api/watch-providers.ts:
--------------------------------------------------------------------------------
1 | import { axiosClient } from '..';
2 | import { Language } from '@/lib/tmdb';
3 | import { GetAvailableRegionsResponse, GetWatchProvidersResponse, WatchProviders } from '@/lib/tmdb';
4 |
5 | /*
6 | |-----------------------------------------------------------------------------
7 | | Watch providers
8 | |
9 | | References:
10 | | 1. https://developer.themoviedb.org/reference/watch-provider-tv-list
11 | | 2. https://developer.themoviedb.org/reference/watch-providers-movie-list
12 | |
13 | |-----------------------------------------------------------------------------
14 | */
15 |
16 | type WatchProvidersQueryParams = {
17 | language: Language;
18 | watch_region?: string;
19 | };
20 |
21 | const list = async (type: 'tv' | 'movie', params: WatchProvidersQueryParams) => {
22 | const { data } = await axiosClient.get(`/watch/providers/${type}`, {
23 | params,
24 | });
25 |
26 | return data.results;
27 | };
28 |
29 | /*
30 | |-----------------------------------------------------------------------------
31 | | Available Regions
32 | |
33 | | References:
34 | | https://developer.themoviedb.org/reference/watch-providers-available-regions
35 | |
36 | |-----------------------------------------------------------------------------
37 | */
38 |
39 | type AvailableRegionsQueryParams = {
40 | language: Language;
41 | };
42 |
43 | const regions = async (params: AvailableRegionsQueryParams) => {
44 | const { data } = await axiosClient.get('/watch/providers/regions', {
45 | params,
46 | });
47 |
48 | return data.results;
49 | };
50 |
51 | /*
52 | |-----------------------------------------------------------------------------
53 | | Item watch providers
54 | |
55 | | References:
56 | | 1. https://developer.themoviedb.org/reference/movie-watch-providers
57 | | 2. https://developer.themoviedb.org/reference/tv-series-watch-providers
58 | |
59 | |-----------------------------------------------------------------------------
60 | */
61 |
62 | const item = async (type: 'tv' | 'movie', id: number) => {
63 | const { data } = await axiosClient.get(`/${type}/${id}/watch/providers`);
64 |
65 | return data;
66 | };
67 |
68 | export const watchProviders = { list, item, regions };
69 |
--------------------------------------------------------------------------------
/lib/tmdb/index.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosError } from 'axios';
2 | import {
3 | collections,
4 | credits,
5 | genres,
6 | images,
7 | keywords,
8 | languages,
9 | movies,
10 | search,
11 | season,
12 | tv,
13 | videos,
14 | watchProviders,
15 | person,
16 | } from '@/lib/tmdb/api';
17 |
18 | const TMDB_API_KEY = process.env.TMDB_API_KEY;
19 |
20 | const handleAxiosError = (error: AxiosError) => {
21 | if (error.response) {
22 | const { data, status, config } = error.response;
23 | console.error('Error fetching data:', {
24 | message: (data as { message?: string }).message || 'No message available',
25 | status,
26 | config,
27 | });
28 | } else {
29 | console.error('Error:', error.message);
30 | }
31 | };
32 |
33 | // Merged client side and server side fetch
34 | export const axiosClient = axios.create({
35 | baseURL: 'https://api.themoviedb.org/3',
36 | params: {
37 | api_key: TMDB_API_KEY,
38 | },
39 | });
40 |
41 | axiosClient.interceptors.response.use(
42 | (response) => response,
43 | (error) => {
44 | handleAxiosError(error);
45 | return Promise.reject(error);
46 | },
47 | );
48 |
49 | export const tmdb = {
50 | collections,
51 | credits,
52 | genres,
53 | images,
54 | keywords,
55 | languages,
56 | movies,
57 | search,
58 | season,
59 | tv,
60 | videos,
61 | watchProviders,
62 | person,
63 | };
64 |
65 | export * from '@/lib/tmdb/models';
66 | export * from '@/lib/tmdb/api';
67 |
--------------------------------------------------------------------------------
/lib/tmdb/models/collections.ts:
--------------------------------------------------------------------------------
1 | import { Movie } from './movie';
2 |
3 | export type Collection = {
4 | id: number;
5 | backdrop_path: string;
6 | name: string;
7 | poster_path: string;
8 | adult: boolean;
9 | original_language: string;
10 | original_name: string;
11 | overview: string;
12 | };
13 |
14 | export type DetailedCollection = Collection & {
15 | parts: Movie[];
16 | };
17 |
--------------------------------------------------------------------------------
/lib/tmdb/models/combined-credits.ts:
--------------------------------------------------------------------------------
1 | import { MovieWithMediaType, TvSerieWithMediaType } from '.';
2 | import { MediaType } from '@/lib/tmdb/utils/with_media_type';
3 |
4 | export type RawMovieCredit = MovieWithMediaType & {
5 | character: string;
6 | };
7 |
8 | export type RawTvSerieCredit = TvSerieWithMediaType & {
9 | character: string;
10 | };
11 |
12 | export type RawCombinedCredit = RawMovieCredit | RawTvSerieCredit;
13 |
14 | export type CombinedCreditsResponse = {
15 | cast: Array;
16 | crew: Array;
17 | };
18 |
19 | export type CombinedCredit = {
20 | id: number;
21 | title: string;
22 | date: string;
23 | media_type: MediaType;
24 | role: string;
25 | vote_average: number;
26 | vote_count: number;
27 | backdrop_path?: string;
28 | };
29 |
30 | export type CombinedCredits = {
31 | cast: Array;
32 | crew: Array;
33 | };
34 |
--------------------------------------------------------------------------------
/lib/tmdb/models/credits.ts:
--------------------------------------------------------------------------------
1 | export type Cast = {
2 | adult: boolean;
3 | gender: number;
4 | id: number;
5 | known_for_department: string;
6 | name: string;
7 | original_name: string;
8 | popularity: number;
9 | profile_path: string;
10 | cast_id: number;
11 | character: string;
12 | credit_id: string;
13 | order: number;
14 | };
15 |
16 | export type Crew = {
17 | adult: boolean;
18 | gender: number;
19 | id: number;
20 | known_for_department: string;
21 | name: string;
22 | original_name: string;
23 | popularity: number;
24 | profile_path: string;
25 | credit_id: string;
26 | department: string;
27 | job: string;
28 | };
29 |
30 | export type Credits = {
31 | id: number;
32 | cast: Cast[];
33 | crew: Crew[];
34 | };
35 |
--------------------------------------------------------------------------------
/lib/tmdb/models/genres.ts:
--------------------------------------------------------------------------------
1 | type Genre = {
2 | id: number;
3 | name: string;
4 | };
5 |
6 | export type GetGenresResponse = {
7 | genres: Genre[];
8 | };
9 |
--------------------------------------------------------------------------------
/lib/tmdb/models/images.ts:
--------------------------------------------------------------------------------
1 | export type Image = {
2 | aspect_ratio: number;
3 | file_path: string;
4 | height: number;
5 | iso_639_1: string;
6 | vote_average: number;
7 | vote_count: number;
8 | width: number;
9 | };
10 |
11 | export type GetImagesResponse = {
12 | id: number;
13 | backdrops: Image[];
14 | logos: Image[];
15 | posters: Image[];
16 | profiles: Image[];
17 | };
18 |
--------------------------------------------------------------------------------
/lib/tmdb/models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './collections';
2 | export * from './combined-credits';
3 | export * from './credits';
4 | export * from './genres';
5 | export * from './images';
6 | export * from './keywords';
7 | export * from './language';
8 | export * from './movie';
9 | export * from './person';
10 | export * from './season';
11 | export * from './tv-series';
12 | export * from './videos';
13 | export * from './watch-providers';
14 |
--------------------------------------------------------------------------------
/lib/tmdb/models/keywords.ts:
--------------------------------------------------------------------------------
1 | export type Keyword = {
2 | name: string;
3 | id: number;
4 | };
5 |
6 | export type GetKeywordsResponse = {
7 | id: number;
8 | results?: Array;
9 | keywords?: Array;
10 | };
11 |
--------------------------------------------------------------------------------
/lib/tmdb/models/language.ts:
--------------------------------------------------------------------------------
1 | export type Language = 'en-US' | 'es-ES' | 'fr-FR' | 'de-DE' | 'it-IT' | 'pt-BR' | 'ja-JP';
2 |
3 | export type GetLanguagesResponse = Array<{
4 | english_name: string;
5 | iso_639_1: string;
6 | name: string;
7 | }>;
8 |
--------------------------------------------------------------------------------
/lib/tmdb/models/movie.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Genre,
3 | ProductionCompany,
4 | ProductionCountry,
5 | SpokenLanguage,
6 | } from '@/lib/tmdb/utils/common';
7 | import { WithMediaType } from '@/lib/tmdb/utils/with_media_type';
8 |
9 | export type BelongsToCollection = {
10 | id: number;
11 | name: string;
12 | poster_path: string;
13 | backdrop_path: string;
14 | };
15 |
16 | export type MovieDetails = {
17 | adult: boolean;
18 | backdrop_path: string;
19 | belongs_to_collection?: BelongsToCollection;
20 | budget: number;
21 | genres: Genre[];
22 | homepage: string;
23 | id: number;
24 | imdb_id: string;
25 | original_language: string;
26 | original_title: string;
27 | overview: string;
28 | popularity: number;
29 | poster_path?: string;
30 | production_companies: ProductionCompany[];
31 | production_countries: ProductionCountry[];
32 | release_date: string;
33 | revenue: number;
34 | runtime: number;
35 | spoken_languages: SpokenLanguage[];
36 | status: string;
37 | tagline: string;
38 | title: string;
39 | video: boolean;
40 | vote_average: number;
41 | vote_count: number;
42 | };
43 |
44 | export type Movie = {
45 | id: number;
46 | poster_path: string;
47 | adult: boolean;
48 | overview: string;
49 | release_date: string;
50 | genre_ids: number[];
51 | original_title: string;
52 | original_language: string;
53 | title: string;
54 | backdrop_path?: string;
55 | popularity: number;
56 | vote_count: number;
57 | video: boolean;
58 | vote_average: number;
59 | };
60 |
61 | export type MovieWithMediaType = WithMediaType;
62 |
--------------------------------------------------------------------------------
/lib/tmdb/models/person.ts:
--------------------------------------------------------------------------------
1 | import { WithMediaType } from '@/lib/tmdb/utils/with_media_type';
2 | import { MovieWithMediaType } from './movie';
3 | import { TvSerieWithMediaType } from './tv-series';
4 |
5 | export type Person = {
6 | id: number;
7 | name: string;
8 | known_for: Array;
9 | profile_path: string;
10 | adult: boolean;
11 | known_for_department: string;
12 | gender: number;
13 | popularity: number;
14 | };
15 | export type PersonWithMediaType = WithMediaType;
16 |
17 | export type PersonDetails = {
18 | adult: boolean;
19 | also_known_as: string[];
20 | birthday: string;
21 | biography: string;
22 | deathday?: string;
23 | gender: number;
24 | homepage?: string;
25 | id: number;
26 | imdb_id: string;
27 | known_for_department: string;
28 | name: string;
29 | place_of_birth: string;
30 | popularity: number;
31 | profile_path: string;
32 | };
33 |
--------------------------------------------------------------------------------
/lib/tmdb/models/season.ts:
--------------------------------------------------------------------------------
1 | import { Crew } from './credits';
2 |
3 | export type GuestStar = {
4 | credit_id: string;
5 | order: number;
6 | character: string;
7 | adult: boolean;
8 | gender: number | null;
9 | id: number;
10 | known_for_department: string;
11 | name: string;
12 | original_name: string;
13 | popularity: number;
14 | profile_path: string | null;
15 | };
16 |
17 | export type Episode = {
18 | air_date: string;
19 | episode_number: number;
20 | crew: Crew[];
21 | guest_stars: GuestStar[];
22 | id: number;
23 | name: string;
24 | overview: string;
25 | production_code: string;
26 | season_number: number;
27 | still_path: string;
28 | vote_average: number;
29 | vote_count: number;
30 | runtime: number;
31 | show_id: number;
32 | };
33 |
34 | export type SeasonDetails = {
35 | air_date: string;
36 | episodes: Episode[];
37 | name: string;
38 | overview: string;
39 | id: number;
40 | poster_path: string | null;
41 | season_number: number;
42 | };
43 |
--------------------------------------------------------------------------------
/lib/tmdb/models/tv-series.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Genre,
3 | ProductionCompany,
4 | ProductionCountry,
5 | SpokenLanguage,
6 | } from '@/lib/tmdb/utils/common';
7 | import { WithMediaType } from '@/lib/tmdb/utils/with_media_type';
8 |
9 | export type TvSerie = {
10 | poster_path: string;
11 | popularity: number;
12 | id: number;
13 | backdrop_path?: string;
14 | vote_average: number;
15 | overview: string;
16 | first_air_date: string;
17 | origin_country: string[];
18 | genre_ids: number[];
19 | original_language: string;
20 | vote_count: number;
21 | name: string;
22 | original_name: string;
23 | };
24 |
25 | export type TvSerieWithMediaType = WithMediaType;
26 |
27 | export type CreatedBy = {
28 | id: number;
29 | credit_id: string;
30 | name: string;
31 | gender: number;
32 | profile_path: string;
33 | };
34 | export type NextEpisodeToAir = {
35 | id: number;
36 | name: string;
37 | overview: string;
38 | vote_average: number;
39 | vote_count: number;
40 | air_date: string;
41 | episode_number: number;
42 | production_code: string;
43 | runtime: number;
44 | season_number: number;
45 | show_id: number;
46 | still_path: string;
47 | };
48 |
49 | export type LastEpisodeToAir = {
50 | air_date: string;
51 | episode_number: number;
52 | id: number;
53 | name: string;
54 | overview: string;
55 | production_code: string;
56 | season_number: number;
57 | still_path: string;
58 | vote_average: number;
59 | vote_count: number;
60 | };
61 |
62 | export type Network = {
63 | name: string;
64 | id: number;
65 | logo_path: string;
66 | origin_country: string;
67 | };
68 |
69 | export type Season = {
70 | air_date: string;
71 | episode_count: number;
72 | id: number;
73 | name: string;
74 | overview: string;
75 | poster_path: string;
76 | season_number: number;
77 | vote_average: number;
78 | };
79 |
80 | export type TvSerieDetails = {
81 | backdrop_path: string;
82 | created_by: CreatedBy[];
83 | episode_run_time: number[];
84 | first_air_date: string;
85 | genres: Genre[];
86 | homepage: string;
87 | id: number;
88 | in_production: boolean;
89 | languages: string[];
90 | last_air_date: string;
91 | last_episode_to_air: LastEpisodeToAir;
92 | name: string;
93 | next_episode_to_air?: NextEpisodeToAir;
94 | networks: Network[];
95 | number_of_episodes: number;
96 | number_of_seasons: number;
97 | origin_country: string[];
98 | original_language: string;
99 | original_name: string;
100 | overview: string;
101 | popularity: number;
102 | poster_path: string;
103 | production_companies: ProductionCompany[];
104 | production_countries: ProductionCountry[];
105 | seasons: Season[];
106 | spoken_languages: SpokenLanguage[];
107 | status: string;
108 | tagline: string;
109 | type: string;
110 | vote_average: number;
111 | vote_count: number;
112 | };
113 |
--------------------------------------------------------------------------------
/lib/tmdb/models/videos.ts:
--------------------------------------------------------------------------------
1 | export type Video = {
2 | id: string;
3 | iso_639_1: string;
4 | iso_3166_1: string;
5 | key: string;
6 | name: string;
7 | site: string;
8 | size: number;
9 | type: string;
10 | };
11 |
12 | export type GetVideosResponse = {
13 | id: number;
14 | results: Video[];
15 | };
16 |
--------------------------------------------------------------------------------
/lib/tmdb/models/watch-providers.ts:
--------------------------------------------------------------------------------
1 | export type Flatrate = {
2 | display_priority: number;
3 | logo_path: string;
4 | provider_id: number;
5 | provider_name: string;
6 | };
7 |
8 | export type Rent = {
9 | display_priority: number;
10 | logo_path: string;
11 | provider_id: number;
12 | provider_name: string;
13 | };
14 |
15 | export type Buy = {
16 | display_priority: number;
17 | logo_path: string;
18 | provider_id: number;
19 | provider_name: string;
20 | };
21 |
22 | type WatchLocaleItem = {
23 | link: string;
24 | flatrate: Flatrate[];
25 | rent: Rent[];
26 | buy: Buy[];
27 | };
28 |
29 | type CountryCode =
30 | | 'AR'
31 | | 'AT'
32 | | 'AU'
33 | | 'BE'
34 | | 'BR'
35 | | 'CA'
36 | | 'CH'
37 | | 'CL'
38 | | 'CO'
39 | | 'CZ'
40 | | 'DE'
41 | | 'DK'
42 | | 'EC'
43 | | 'EE'
44 | | 'ES'
45 | | 'FI'
46 | | 'FR'
47 | | 'GB'
48 | | 'GR'
49 | | 'HU'
50 | | 'ID'
51 | | 'IE'
52 | | 'IN'
53 | | 'IT'
54 | | 'JP'
55 | | 'KR'
56 | | 'LT'
57 | | 'LV'
58 | | 'MX'
59 | | 'MY'
60 | | 'NL'
61 | | 'NO'
62 | | 'NZ'
63 | | 'PE'
64 | | 'PH'
65 | | 'PL'
66 | | 'PT'
67 | | 'RO'
68 | | 'RU'
69 | | 'SE'
70 | | 'SG'
71 | | 'TH'
72 | | 'TR'
73 | | 'US'
74 | | 'VE'
75 | | 'ZA';
76 |
77 | export type WatchLocale = {
78 | [key in CountryCode]: WatchLocaleItem;
79 | };
80 |
81 | export type WatchProviders = {
82 | id: number;
83 | results: WatchLocale;
84 | };
85 |
86 | export type GetWatchProvidersResponse = {
87 | results: Array<{
88 | display_priorities: Record;
89 | display_priority: number;
90 | logo_path: string;
91 | provider_name: string;
92 | provider_id: number;
93 | }>;
94 | };
95 |
96 | export type GetAvailableRegionsResponse = {
97 | results: Array<{
98 | english_name: string;
99 | iso_3166_1: string;
100 | native_name: string;
101 | }>;
102 | };
103 |
--------------------------------------------------------------------------------
/lib/tmdb/utils/common.ts:
--------------------------------------------------------------------------------
1 | export type ProductionCountry = {
2 | iso_3166_1: string;
3 | name: string;
4 | };
5 |
6 | export type ProductionCompany = {
7 | id: number;
8 | logo_path: string;
9 | name: string;
10 | origin_country: string;
11 | };
12 |
13 | export type SpokenLanguage = {
14 | english_name: string;
15 | iso_639_1: string;
16 | name: string;
17 | };
18 |
19 | export type Genre = {
20 | id: number;
21 | name: string;
22 | };
23 |
--------------------------------------------------------------------------------
/lib/tmdb/utils/format-combined-credit.ts:
--------------------------------------------------------------------------------
1 | import type { CombinedCredit, RawMovieCredit, RawTvSerieCredit } from '@/lib/tmdb';
2 |
3 | export const formatCombinedCredit = (credit: RawMovieCredit | RawTvSerieCredit): CombinedCredit => {
4 | if ((credit as RawTvSerieCredit).name) {
5 | const {
6 | first_air_date: date,
7 | id,
8 | name,
9 | character,
10 | vote_average,
11 | vote_count,
12 | backdrop_path,
13 | } = credit as RawTvSerieCredit;
14 |
15 | return {
16 | date,
17 | id,
18 | title: name,
19 | media_type: 'tv',
20 | role: character,
21 | vote_average,
22 | vote_count,
23 | backdrop_path,
24 | };
25 | }
26 |
27 | const {
28 | title,
29 | id,
30 | character,
31 | release_date: date,
32 | vote_average,
33 | vote_count,
34 | backdrop_path,
35 | } = credit as RawMovieCredit;
36 |
37 | return {
38 | title,
39 | id,
40 | date,
41 |
42 | media_type: 'movie',
43 | role: character,
44 | vote_count,
45 | vote_average,
46 | backdrop_path,
47 | };
48 | };
49 |
--------------------------------------------------------------------------------
/lib/tmdb/utils/list-response.ts:
--------------------------------------------------------------------------------
1 | export type ListResponse = {
2 | page: number;
3 | results: T[];
4 | total_results: number;
5 | total_pages: number;
6 | };
7 |
--------------------------------------------------------------------------------
/lib/tmdb/utils/with_media_type.ts:
--------------------------------------------------------------------------------
1 | export type MediaType = 'tv' | 'movie' | 'person';
2 | export type WithMediaType = T & {
3 | media_type: K;
4 | };
5 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from 'clsx';
2 | import { twMerge } from 'tailwind-merge';
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { headers } from 'next/headers';
2 | import { auth } from '@/lib/auth';
3 | import { NextRequest, NextResponse } from 'next/server';
4 |
5 | const redirects = {
6 | patterns: [
7 | '1196943',
8 | '1370264',
9 | '251898',
10 | '43971',
11 | '14159',
12 | '188306',
13 | '15864',
14 | '37822',
15 | '52696',
16 | '456756',
17 | '399624',
18 | '254171',
19 | '666390',
20 | '11763',
21 | '14163',
22 | '32680',
23 | '291154',
24 | '35053',
25 | '320295',
26 | '17478',
27 | '14194',
28 | '44977',
29 | '22609',
30 | '13986',
31 | '19404',
32 | '210915',
33 | '325138',
34 | '85985',
35 | '192534',
36 | '377985',
37 | '8453',
38 | '215211',
39 | '250551',
40 | '493623',
41 | '4253',
42 | '103640',
43 | '132316',
44 | '678999',
45 | '14162',
46 | '30627',
47 | '73443',
48 | '30244',
49 | '296690',
50 | '25638',
51 | '82698',
52 | '44613',
53 | '64879',
54 | '63683',
55 | '591101',
56 | '287767',
57 | '206202',
58 | '74458',
59 | '179711',
60 | '139656',
61 | '455470',
62 | '11518',
63 | '26277',
64 | '206198',
65 | '46415',
66 | '19616',
67 | '204848',
68 | '864692',
69 | '29538',
70 | '472470',
71 | '14072',
72 | '26815',
73 | '20656',
74 | '15084',
75 | '611598',
76 | '534081',
77 | '206410',
78 | '539686',
79 | '209410',
80 | '28472',
81 | '496327',
82 | '376812',
83 | '14165',
84 | '14193',
85 | '984092',
86 | '206821',
87 | '14214',
88 | '472138',
89 | '720557',
90 | '441909',
91 | '266038',
92 | '4251',
93 | '585268',
94 | '161447',
95 | ],
96 | redirect_to: '/removed',
97 | };
98 |
99 | export async function middleware(request: NextRequest) {
100 | const urlPath = request.nextUrl.pathname;
101 |
102 | const shouldRedirect = redirects.patterns.some((pattern) => urlPath.includes(pattern));
103 | if (shouldRedirect) {
104 | return NextResponse.redirect(new URL(redirects.redirect_to, request.url));
105 | }
106 |
107 | const session = await auth.api.getSession({
108 | headers: await headers(),
109 | });
110 |
111 | if (!session) {
112 | return NextResponse.redirect(new URL('/sign-in', request.url));
113 | }
114 |
115 | return NextResponse.next();
116 | }
117 |
118 | export const config = {
119 | matcher: ['/profile/[id]'],
120 | };
121 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | remotePatterns: [
5 | {
6 | hostname: 'media.kitsu.io',
7 | port: '',
8 | pathname: '/**',
9 | protocol: 'https',
10 | },
11 | {
12 | hostname: 'media.kitsu.app',
13 | port: '',
14 | pathname: '/**',
15 | protocol: 'https',
16 | },
17 | {
18 | protocol: 'https',
19 | hostname: 'asianimg.pro',
20 | pathname: '/cover/**',
21 | },
22 | {
23 | protocol: 'https',
24 | hostname: 'www.pngall.com',
25 | },
26 | {
27 | protocol: 'https',
28 | hostname: 'gogocdn.net',
29 | },
30 | {
31 | protocol: 'https',
32 | hostname: 'asianimg.pro',
33 | },
34 | {
35 | protocol: 'https',
36 | hostname: 's4.anilist.co',
37 | },
38 | {
39 | protocol: 'https',
40 | hostname: 'image.tmdb.org',
41 | },
42 | {
43 | protocol: 'https',
44 | hostname: 'artworks.thetvdb.com',
45 | },
46 | {
47 | protocol: 'https',
48 | hostname: 'dramacool.bg',
49 | },
50 | ],
51 | },
52 | experimental: {
53 | serverActions: {
54 | allowedOrigins: ['localhost:3000'],
55 | },
56 | },
57 | env: {
58 | TMDB_API_KEY: process.env.TMDB_API_KEY,
59 | },
60 | async rewrites() {
61 | return [
62 | {
63 | // Proxy /api/tmdb requests to TMDB API to hide API key
64 | source: '/api/tmdb/:path*',
65 | destination: `https://api.themoviedb.org/3/:path*?api_key=${process.env.TMDB_API_KEY}`,
66 | },
67 | ];
68 | },
69 | };
70 |
71 | module.exports = nextConfig;
72 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | '@tailwindcss/postcss': {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/DEPLOY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/DEPLOY.png
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-144-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/android/android-launchericon-144-144.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-192-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/android/android-launchericon-192-192.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-48-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/android/android-launchericon-48-48.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-512-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/android/android-launchericon-512-512.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-72-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/android/android-launchericon-72-72.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-96-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/android/android-launchericon-96-96.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/avalynndev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/avalynndev.png
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/favicon.ico
--------------------------------------------------------------------------------
/public/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/image.png
--------------------------------------------------------------------------------
/public/ios/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/100.png
--------------------------------------------------------------------------------
/public/ios/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/1024.png
--------------------------------------------------------------------------------
/public/ios/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/114.png
--------------------------------------------------------------------------------
/public/ios/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/120.png
--------------------------------------------------------------------------------
/public/ios/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/128.png
--------------------------------------------------------------------------------
/public/ios/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/144.png
--------------------------------------------------------------------------------
/public/ios/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/152.png
--------------------------------------------------------------------------------
/public/ios/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/16.png
--------------------------------------------------------------------------------
/public/ios/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/167.png
--------------------------------------------------------------------------------
/public/ios/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/180.png
--------------------------------------------------------------------------------
/public/ios/192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/192.png
--------------------------------------------------------------------------------
/public/ios/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/20.png
--------------------------------------------------------------------------------
/public/ios/256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/256.png
--------------------------------------------------------------------------------
/public/ios/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/29.png
--------------------------------------------------------------------------------
/public/ios/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/32.png
--------------------------------------------------------------------------------
/public/ios/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/40.png
--------------------------------------------------------------------------------
/public/ios/50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/50.png
--------------------------------------------------------------------------------
/public/ios/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/512.png
--------------------------------------------------------------------------------
/public/ios/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/57.png
--------------------------------------------------------------------------------
/public/ios/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/58.png
--------------------------------------------------------------------------------
/public/ios/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/60.png
--------------------------------------------------------------------------------
/public/ios/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/64.png
--------------------------------------------------------------------------------
/public/ios/72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/72.png
--------------------------------------------------------------------------------
/public/ios/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/76.png
--------------------------------------------------------------------------------
/public/ios/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/80.png
--------------------------------------------------------------------------------
/public/ios/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/ios/87.png
--------------------------------------------------------------------------------
/public/manga.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/manga.png
--------------------------------------------------------------------------------
/public/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/mstile-144x144.png
--------------------------------------------------------------------------------
/public/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/mstile-150x150.png
--------------------------------------------------------------------------------
/public/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/mstile-310x150.png
--------------------------------------------------------------------------------
/public/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/mstile-310x310.png
--------------------------------------------------------------------------------
/public/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/mstile-70x70.png
--------------------------------------------------------------------------------
/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.14, written by Peter Selinger 2001-2017
9 |
10 |
12 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/public/windows11/LargeTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/LargeTile.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/LargeTile.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/LargeTile.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/LargeTile.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/LargeTile.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/LargeTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/LargeTile.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/LargeTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/LargeTile.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/SmallTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/SmallTile.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/SmallTile.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/SmallTile.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/SmallTile.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/SmallTile.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/SmallTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/SmallTile.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/SmallTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/SmallTile.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/SplashScreen.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/SplashScreen.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/SplashScreen.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/SplashScreen.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/SplashScreen.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/SplashScreen.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/SplashScreen.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/SplashScreen.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/Square150x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square150x150Logo.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/Square150x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square150x150Logo.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/Square150x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square150x150Logo.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/Square150x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square150x150Logo.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-16.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-20.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-24.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-256.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-30.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-32.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-36.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-40.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-44.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-48.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-60.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-64.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-72.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-80.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.altform-unplated_targetsize-96.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-16.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-20.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-24.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-256.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-30.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-32.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-36.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-40.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-44.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-48.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-60.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-64.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-72.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-80.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Square44x44Logo.targetsize-96.png
--------------------------------------------------------------------------------
/public/windows11/StoreLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/StoreLogo.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/StoreLogo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/StoreLogo.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/StoreLogo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/StoreLogo.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/StoreLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/StoreLogo.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/StoreLogo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/StoreLogo.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/Wide310x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Wide310x150Logo.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/Wide310x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Wide310x150Logo.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/Wide310x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Wide310x150Logo.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/Wide310x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avalynndev/enjoytown/dfa913d694e1512bf50c8e1ee6558d2811125639/public/windows11/Wide310x150Logo.scale-400.png
--------------------------------------------------------------------------------
/schema/index.ts:
--------------------------------------------------------------------------------
1 | import { pgTable, text, timestamp, boolean, integer } from 'drizzle-orm/pg-core';
2 | import { randomUUID } from 'crypto';
3 |
4 | export const user = pgTable('user', {
5 | id: text('id').primaryKey(),
6 | name: text('name').notNull(),
7 | email: text('email').notNull().unique(),
8 | emailVerified: boolean('email_verified').notNull(),
9 | image: text('image'),
10 | createdAt: timestamp('created_at').notNull(),
11 | updatedAt: timestamp('updated_at').notNull(),
12 | username: text('username').unique(),
13 | displayUsername: text('display_username'),
14 | });
15 |
16 | export const session = pgTable('session', {
17 | id: text('id').primaryKey(),
18 | expiresAt: timestamp('expires_at').notNull(),
19 | token: text('token').notNull().unique(),
20 | createdAt: timestamp('created_at').notNull(),
21 | updatedAt: timestamp('updated_at').notNull(),
22 | ipAddress: text('ip_address'),
23 | userAgent: text('user_agent'),
24 | userId: text('user_id')
25 | .notNull()
26 | .references(() => user.id, { onDelete: 'cascade' }),
27 | });
28 |
29 | export const account = pgTable('account', {
30 | id: text('id').primaryKey(),
31 | accountId: text('account_id').notNull(),
32 | providerId: text('provider_id').notNull(),
33 | userId: text('user_id')
34 | .notNull()
35 | .references(() => user.id, { onDelete: 'cascade' }),
36 | accessToken: text('access_token'),
37 | refreshToken: text('refresh_token'),
38 | idToken: text('id_token'),
39 | accessTokenExpiresAt: timestamp('access_token_expires_at'),
40 | refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
41 | scope: text('scope'),
42 | password: text('password'),
43 | createdAt: timestamp('created_at').notNull(),
44 | updatedAt: timestamp('updated_at').notNull(),
45 | });
46 |
47 | export const verification = pgTable('verification', {
48 | id: text('id').primaryKey(),
49 | identifier: text('identifier').notNull(),
50 | value: text('value').notNull(),
51 | expiresAt: timestamp('expires_at').notNull(),
52 | createdAt: timestamp('created_at'),
53 | updatedAt: timestamp('updated_at'),
54 | });
55 |
56 | export const feedback = pgTable('feedback', {
57 | id: text('id').default(randomUUID()),
58 | type: text('type').notNull(),
59 | name: text('name'),
60 | url: text('url'),
61 | issue: text('issue'),
62 | reason: text('reason'),
63 | logs: text('logs'),
64 | message: text('message'),
65 | email: text('email'),
66 | createdAt: timestamp('created_at').notNull().defaultNow(),
67 | });
68 |
69 | export const schema = { user, session, account, verification, feedback };
70 |
--------------------------------------------------------------------------------
/styles/loading.css:
--------------------------------------------------------------------------------
1 | .loader {
2 | --path: #262933;
3 | --dot: #5628ee;
4 | --duration: 3s;
5 | width: 44px;
6 | height: 44px;
7 | position: relative;
8 | }
9 |
10 | .loader:before {
11 | content: '';
12 | width: 6px;
13 | height: 6px;
14 | border-radius: 50%;
15 | position: absolute;
16 | display: block;
17 | background: var(--dot);
18 | top: 37px;
19 | left: 19px;
20 | transform: translate(-18px, -18px);
21 | animation: dotRect var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
22 | }
23 |
24 | .loader svg {
25 | display: block;
26 | width: 100%;
27 | height: 100%;
28 | }
29 |
30 | .loader svg rect,
31 | .loader svg polygon,
32 | .loader svg circle {
33 | fill: none;
34 | stroke: var(--path);
35 | stroke-width: 10px;
36 | stroke-linejoin: round;
37 | stroke-linecap: round;
38 | }
39 |
40 | .loader svg polygon {
41 | stroke-dasharray: 145 76 145 76;
42 | stroke-dashoffset: 0;
43 | animation: pathTriangle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
44 | }
45 |
46 | .loader svg rect {
47 | stroke-dasharray: 192 64 192 64;
48 | stroke-dashoffset: 0;
49 | animation: pathRect 3s cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
50 | }
51 |
52 | .loader svg circle {
53 | stroke-dasharray: 150 50 150 50;
54 | stroke-dashoffset: 75;
55 | animation: pathCircle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
56 | }
57 |
58 | .loader.triangle {
59 | width: 48px;
60 | }
61 |
62 | .loader.triangle:before {
63 | left: 21px;
64 | transform: translate(-10px, -18px);
65 | animation: dotTriangle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
66 | }
67 |
68 | @keyframes pathTriangle {
69 | 33% {
70 | stroke-dashoffset: 74;
71 | }
72 |
73 | 66% {
74 | stroke-dashoffset: 147;
75 | }
76 |
77 | 100% {
78 | stroke-dashoffset: 221;
79 | }
80 | }
81 |
82 | @keyframes dotTriangle {
83 | 33% {
84 | transform: translate(0, 0);
85 | }
86 |
87 | 66% {
88 | transform: translate(10px, -18px);
89 | }
90 |
91 | 100% {
92 | transform: translate(-10px, -18px);
93 | }
94 | }
95 |
96 | @keyframes pathRect {
97 | 25% {
98 | stroke-dashoffset: 64;
99 | }
100 |
101 | 50% {
102 | stroke-dashoffset: 128;
103 | }
104 |
105 | 75% {
106 | stroke-dashoffset: 192;
107 | }
108 |
109 | 100% {
110 | stroke-dashoffset: 256;
111 | }
112 | }
113 |
114 | @keyframes dotRect {
115 | 25% {
116 | transform: translate(0, 0);
117 | }
118 |
119 | 50% {
120 | transform: translate(18px, -18px);
121 | }
122 |
123 | 75% {
124 | transform: translate(0, -36px);
125 | }
126 |
127 | 100% {
128 | transform: translate(-18px, -18px);
129 | }
130 | }
131 |
132 | @keyframes pathCircle {
133 | 25% {
134 | stroke-dashoffset: 125;
135 | }
136 |
137 | 50% {
138 | stroke-dashoffset: 175;
139 | }
140 |
141 | 75% {
142 | stroke-dashoffset: 225;
143 | }
144 |
145 | 100% {
146 | stroke-dashoffset: 275;
147 | }
148 | }
149 |
150 | .loader {
151 | display: inline-block;
152 | margin: 0 16px;
153 | }
154 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/types/index.ts:
--------------------------------------------------------------------------------
1 | import { Icons } from '@/components/common/icons';
2 | import { Genre } from '@/lib/tmdb/utils/common';
3 |
4 | export interface AnimeShow {
5 | id: string;
6 | malId: number;
7 | title: {
8 | romaji: string;
9 | english: string;
10 | native: string;
11 | userPreferred: string;
12 | };
13 | image: string;
14 | trailer: {
15 | id: string;
16 | site: string;
17 | thumbnail: string;
18 | thumbnailHash: string;
19 | };
20 | description: string;
21 | status: string;
22 | cover: string;
23 | rating: number;
24 | releaseDate: number;
25 | color: string;
26 | genres: string[];
27 | totalEpisodes: number;
28 | duration: number;
29 | type: string;
30 | }
31 |
32 | export interface Show {
33 | id: number;
34 | poster_path: string;
35 | overview: string;
36 | genre_ids: number[];
37 | original_language: string;
38 | popularity: number;
39 | vote_count: number;
40 | vote_average: number;
41 | backdrop_path?: string;
42 | // Movie-specific properties
43 | adult?: boolean;
44 | video?: boolean;
45 | title?: string;
46 | original_title?: string;
47 | release_date?: string;
48 | // TvSerie-specific properties
49 | name?: string;
50 | original_name?: string;
51 | first_air_date?: string;
52 | origin_country?: string[];
53 | genres?: Genre[];
54 | }
55 |
56 | export interface data_types {
57 | title: string;
58 | image: string;
59 | episode_number: number;
60 | type: string;
61 | }
62 |
63 | export interface Anime {
64 | id: number;
65 | title: string;
66 | }
67 |
68 | export interface EpisodeDetails {
69 | id: number;
70 | title: string;
71 | }
72 |
73 | export interface WatchDataSources {
74 | url: string;
75 | isM3U8: boolean;
76 | quality: string;
77 | }
78 |
79 | export interface WatchData {
80 | sources: {
81 | url: string;
82 | }[];
83 | }
84 |
85 | export interface NavItem {
86 | title: string;
87 | href?: string;
88 | disabled?: boolean;
89 | external?: boolean;
90 | icon?: keyof typeof Icons;
91 | label?: string;
92 | }
93 |
94 | export interface NavItemWithChildren extends NavItem {
95 | items: NavItemWithChildren[];
96 | }
97 |
98 | export interface MainNavItem extends NavItem {}
99 |
100 | export interface SidebarNavItem extends NavItemWithChildren {}
101 |
102 | export interface MainNavProps {
103 | items?: NavItem[];
104 | }
105 |
106 | export interface DramaInfo {
107 | episodes: {
108 | id: string;
109 | title: string;
110 | episode: number;
111 | subType: string;
112 | releaseDate: string;
113 | url: string;
114 | }[];
115 | }
116 |
--------------------------------------------------------------------------------