diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/browse/page.tsx | 153 | ||||
| -rw-r--r-- | app/components/AuthContext.tsx | 25 | ||||
| -rw-r--r-- | app/components/Genre.tsx | 24 | ||||
| -rw-r--r-- | app/components/Modal.tsx | 66 | ||||
| -rw-r--r-- | app/components/PreviewCard.tsx | 20 | ||||
| -rw-r--r-- | app/hello/page.tsx | 99 | ||||
| -rw-r--r-- | app/layout.js | 20 | ||||
| -rw-r--r-- | app/login/page.tsx | 125 | ||||
| -rw-r--r-- | app/password-recovery/page.tsx | 44 | ||||
| -rw-r--r-- | app/profiles/page.tsx | 56 | ||||
| -rw-r--r-- | app/register/page.tsx | 123 |
11 files changed, 755 insertions, 0 deletions
diff --git a/app/browse/page.tsx b/app/browse/page.tsx new file mode 100644 index 0000000..7806738 --- /dev/null +++ b/app/browse/page.tsx @@ -0,0 +1,153 @@ +'use client'
+import { link } from 'fs'
+import Link from 'next/link'
+import { BellIcon, MagnifyingGlassIcon, PlayIcon, ArrowSmallDownIcon} from '@heroicons/react/24/solid'
+import { InformationCircleIcon} from '@heroicons/react/24/outline'
+import { useEffect, useState, useContext } from 'react'
+import requests from '../../services/requests'
+import Image from "next/image"
+import PreviewCard from '../components/PreviewCard'
+import Modal from '../components/Modal'
+import { auth } from '../../services/firebase';
+import { useRouter } from 'next/navigation';
+import { signOut } from 'firebase/auth';
+import { AuthContext } from '../components/AuthContext'
+
+// import NetflixLogo from '../../../public/images/netflix_logo.svg'
+const baseURL ='https://api.themoviedb.org/3/'
+const imageURL = 'https://image.tmdb.org/t/p/original'
+
+const Home = () => {
+ const router = useRouter();
+ const [heroMovie, setHeroMovie] = useState()
+ const [trendingMovies, setTrendingMovies] = useState()
+ const [trendingTv, setTrendingTv] = useState()
+ const [modalVisible, setModalVisible] = useState(false)
+ const [modalContent, setModalContent] = useState()
+ const user = useContext(AuthContext);
+
+ useEffect(() => {
+ // Check if user is authenticated
+ if (!user) {
+ // Redirect or perform any necessary action
+ router.push('/login');
+ } else {
+ // User is authenticated, continue with desired logic
+ }
+ }, [user]);
+
+ useEffect(() => {
+ const user = auth.currentUser;
+ if (!user) {
+ // User is not signed in, redirect to the login page
+ router.push('/login');
+ }
+ }, []);
+
+ const logoutUser = () => {
+ signOut(auth)
+ .then(() => {
+ console.log('User signed Out!');
+ // Navigate to the home screen or other desired screen
+ router.push('/login');
+ })
+ .catch(error => {
+ console.error(error);
+ // Display an error message to the user
+ });
+ }
+
+ useEffect(() => {
+ fetch(`${baseURL}${requests.fetchTopRated}`).then(res => res.json()).then((data) => {
+ setHeroMovie(data.results[3])
+ })
+ }, [])
+ useEffect(() => {
+ fetch(`${baseURL}${requests.fetchTrendingMovies}`).then(res => res.json()).then((data) => {
+ setTrendingMovies(data.results)
+ console.log(data.results)
+ })
+ }, [])
+ useEffect(() => {
+ fetch(`${baseURL}${requests.fetchTrendingTv}`).then(res => res.json()).then((data) => {
+ setTrendingTv(data.results)
+ })
+ }, [])
+
+ return (
+ <div className="flex flex-col text-white justify-between gap-10 h-screen">
+ <header className='flex justify-between pt-3 px-8 z-10 text-xs'>
+ <div className='flex items-center gap-4'>
+ <img className='scale-[65%] mr-4' src="../images/netflix_logo.svg" alt="SVG image"/>
+ <a className='text-white font-bold'>Home</a>
+ <a className='text-white'>TV Shows</a>
+ <a className='text-white'>Movies</a>
+ <a className='text-white'>News & Popular</a>
+ <a className='text-white'>My List</a>
+ <a className='text-white'>Browse by Language</a>
+ </div>
+ <div className='flex items-center gap-4'>
+ <MagnifyingGlassIcon className='h-4 w-4'/>
+ <a className='text-white'>Kids</a>
+ <BellIcon className='h-4 w-4'/>
+ <div className='h-8 w-8'>
+ <Link href='../' className="w-[128px] h-[128px] rounded-xl overflow-hidden bg-white">
+ <img src='https://avatars.dicebear.com/api/male/124.svg' alt=""/>
+ </Link>
+ </div>
+ <ArrowSmallDownIcon className='h-4 w-4 cursor-pointer' onClick={logoutUser}/>
+ </div>
+ </header>
+ <section className='w-1/3 ml-10'>
+ <div className='flex items-center'>
+ <img className='scale-[65%] mr-5' src="../images/N.svg" alt="SVG image"/>
+ <h1 className='text-sm font-mono tracking-[.4rem]'>MOVIES</h1>
+ </div>
+ <div className='h-fit text-[3rem]'>
+ {heroMovie?.original_title}
+ </div>
+ <div className='flex items-center'>
+ <img className='scale-[45%]' src="../images/Top10.svg" alt="SVG image"/>
+ <h2 className='font-bold'>#1 in TV Shows Today</h2>
+ </div>
+ <div>
+ <p className=' text-sm break-words text-justify'>
+ {heroMovie?.overview}
+ </p>
+ </div>
+ <div className='flex gap-4 mt-4'>
+ <button className='flex items-center p-4 h-10 bg-white text-black text-xl justify-center font-semibold rounded hover:outline outline-offset-4 outline-4 outline-white'>
+ <PlayIcon className='h-6 w-6 mr-2'/>Play
+ </button>
+ <button className='flex items-center p-4 h-10 bg-gray-700/80 justify-center rounded hover:outline outline-offset-4 outline-4'>
+ <InformationCircleIcon className='h-6 w-6 mr-2'/>More Info
+ </button>
+ </div>
+ </section>
+ <section className=' ml-10 mb-10 z-10'>
+ <h2>Popular on Netflix</h2>
+ <div className='flex flex-row gap-3 overflow-x-auto mt-4 items-center p-6 pl-10 -ml-10'>
+ {
+ trendingMovies?.map((movie, index) => (
+ <PreviewCard key={index} movie={movie} setModalContentId={setModalContent} setModalVisible={setModalVisible}/>
+ ))}
+ </div>
+ </section>
+ <section className=' ml-10 mb-10 z-10'>
+ <h2>Tv Shows</h2>
+ <div className='flex flex-row gap-3 overflow-x-auto mt-4 items-center p-5 pl-10 -ml-10'>
+ {
+ trendingTv?.map((movie, index) => (
+ <PreviewCard key={index} movie={movie} setModalContentId={setModalContent} setModalVisible={setModalVisible}/>
+ ))}
+ </div>
+ </section>
+ <Modal modalContent={modalContent} setModalVisible={setModalVisible} modalVisible={modalVisible}/>
+ <div className='absolute h-full w-full bg-contain bg-right -z-20 bg-no-repeat' style={{backgroundImage: `url(${imageURL}${heroMovie?.backdrop_path})`}}></div>
+ <div className='absolute w-full h-32 -z-10 bg-gradient-to-b from-black'></div>
+ <div className='absolute bottom-0 w-full h-20 -z-10 bg-gradient-to-t from-black'></div>
+ <div className='absolute w-2/3 h-full -z-10 bg-gradient-to-r from-black'></div>
+ </div>
+ )
+}
+export default Home;
\ No newline at end of file diff --git a/app/components/AuthContext.tsx b/app/components/AuthContext.tsx new file mode 100644 index 0000000..9c83744 --- /dev/null +++ b/app/components/AuthContext.tsx @@ -0,0 +1,25 @@ +'use client'
+import { createContext, useState, useEffect } from 'react';
+import { onAuthStateChanged } from 'firebase/auth';
+import { auth } from '../../services/firebase';
+
+export const AuthContext = createContext();
+
+export const AuthProvider = ({ children }) => {
+ const [user, setUser] = useState(null);
+
+ useEffect(() => {
+ const unsubscribe = onAuthStateChanged(auth, (user) => {
+ setUser(user);
+ });
+
+ // Cleanup the subscription when the component unmounts
+ return () => unsubscribe();
+ }, []);
+
+ return (
+ <AuthContext.Provider value={user}>
+ {children}
+ </AuthContext.Provider>
+ );
+};
\ No newline at end of file diff --git a/app/components/Genre.tsx b/app/components/Genre.tsx new file mode 100644 index 0000000..13e7ed2 --- /dev/null +++ b/app/components/Genre.tsx @@ -0,0 +1,24 @@ +import { useEffect, useState } from "react"
+
+const Genres = ({id}) => {
+
+ const [genres, setGenres] = useState()
+
+ useEffect(() => {
+ fetch(`https://api.themoviedb.org/3/movie/${id}?api_key=8216fbb9997cd81a67471e6cb5a6f2df`).then((res) => res.json()).then((data) => {
+ setGenres(data?.genres)
+ })
+ }, [id])
+
+ return (
+ <div className="text-white/60 gap-x-3 text-xs flex flex-row flex-wrap relative">
+ {genres?.map((genre, index) => {
+ return (
+ <div key={index}>{genre.name}</div>
+ )
+ })}
+ </div>
+ )
+}
+
+export default Genres
\ No newline at end of file diff --git a/app/components/Modal.tsx b/app/components/Modal.tsx new file mode 100644 index 0000000..e783cfa --- /dev/null +++ b/app/components/Modal.tsx @@ -0,0 +1,66 @@ +import { useEffect, useState } from 'react'
+import { XMarkIcon } from '@heroicons/react/24/outline'
+import Genre from './Genre'
+
+const baseURL = 'https://api.themoviedb.org/3/'
+const API_KEY = '8216fbb9997cd81a67471e6cb5a6f2df'
+const Modal = ({ modalVisible, modalContent, setModalVisible, type }) => {
+ const [content, setContent] = useState()
+ const [video, setVideo] = useState()
+ const [similarMovies, setSimilarMovies] = useState()
+ useEffect(() => {
+ fetch(`${baseURL}/movie/${modalContent?.id}/similar?api_key=${API_KEY}`).then(res => res.json()).then((data) => {
+ setSimilarMovies(data.results)
+ })
+ }, [modalContent])
+ useEffect(() => {
+ if (modalContent) {
+ fetch(`${baseURL}/${modalContent.media_type}/${modalContent.id}/videos?api_key=${API_KEY}`)
+ .then((res) => res.json())
+ .then((data) => {
+ console.log('VIDEOS DATA', data.results[0].key)
+ setVideo(data.results[0].key)
+ })
+}
+ }, [modalContent])
+ return (
+ <div
+ className={`backdrop-blur-[2px] fixed inset-0 z-50 bg-black/40 flex items-center justify-center ${modalVisible ? '' : 'hidden'
+ }`} onClick={(e) => e.target === e.currentTarget && setModalVisible(false)}
+ >
+ <div className="flex flex-col bg-transparent shadow-2xl rounded-xl z-100">
+ {modalVisible && (
+ <iframe
+ className={'w-full bg-black'}
+ width="420"
+ height="315"
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
+ src={`https://www.youtube.com/embed/${video}?autoplay=1`}
+ ></iframe>
+ )}
+ <div className={'px-8 py-4 w-[600px] bg-gray-800'}>
+ <Genre id={modalContent?.id} />
+ <h3 className={'text-xl'}>{modalContent?.original_name}{modalContent?.original_title}</h3>
+ <p className='w-full break-words h-[70px] overflow-y-auto'>{modalContent?.overview}</p>
+ <hr className='my-4'/>
+ <div className='flex flex-row overflow-x-auto gap-8'>
+ {
+ similarMovies?.map((movie, index) => (
+
+
+ <div
+ className={`
+ shrink-0 bg-contain bg-no-repeat w-[150px] h-[112px] bg-center z-40 ${movie?.backdrop_path === null ? 'hidden' : '' }
+ `}
+ style={{
+ backgroundImage: `url(https://image.tmdb.org/t/p/original${movie?.backdrop_path})`,
+ }}
+ ></div>
+ ))}
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+}
+export default Modal
diff --git a/app/components/PreviewCard.tsx b/app/components/PreviewCard.tsx new file mode 100644 index 0000000..132f50a --- /dev/null +++ b/app/components/PreviewCard.tsx @@ -0,0 +1,20 @@ +const imageURL = 'https://image.tmdb.org/t/p/original';
+import Genre from "./Genre";
+const baseURL ='https://api.themoviedb.org/3/'
+
+const PreviewCard = ({ movie, setModalContentId, setModalVisible }) => {
+ return (
+ <div className="hover:z-10" onClick={ () => {setModalVisible(true), setModalContentId(movie)}}>
+ <div className="shadow-black hover:outline outline-offset-4 outline-4 outline-white rounded flex-col p-1 relative bg-cover h-32 w-60 shrink-0 flex justify-end cursor-pointer hover:scale-110 transition ease-in-out" style={{backgroundImage: `url(${imageURL}${movie?.backdrop_path})`}}>
+ <div className="absolute h-full w-full inset-0 bg-gradient-to-t from-black/70"></div>
+ <h3 className={"text-white text-sm relative"}>
+ {movie.original_title}
+ {movie.original_name}
+ </h3>
+ <Genre id={movie.id}/>
+ </div>
+ </div>
+ )}
+
+
+export default PreviewCard
\ No newline at end of file diff --git a/app/hello/page.tsx b/app/hello/page.tsx new file mode 100644 index 0000000..89e730e --- /dev/null +++ b/app/hello/page.tsx @@ -0,0 +1,99 @@ +'use client'
+import { signInWithEmailAndPassword } from "firebase/auth";
+import Link from "next/link";
+import { auth } from "../../services/firebase";
+
+export default function Hello() {
+ let email = ''
+ let isValidEmail = ''
+ let setIsValidEmail = ''
+ let password = ''
+ let isValidPassword = ''
+ let setIsValidPassword = ''
+ let setIsPasswordVisible = ''
+ let se
+
+ const emailValidation = () => {
+ const emailRegex = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
+ if (!email || emailRegex.test(email) === false) {
+ setIsValidEmail(false);
+ return false;
+ }
+ setIsValidEmail(true);
+ return true;
+ };
+
+ const passwordValidation = () => {
+ const passwordRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$/i;
+
+ if (!password || passwordRegex.test(password) === false) {
+ setIsValidPassword(false)
+ return false
+ }
+ setIsValidPassword(true)
+ return true
+ }
+
+ const debounce = fn => {
+ let id = null;
+
+ return (...args) => {
+ if (id) {
+ clearTimeout(id);
+ }
+ id = setTimeout(() => {
+ fn(...args);
+ id = null;
+ }, 300);
+ };
+ };
+
+ const loginUser = () => {
+ if (isValidEmail && isValidPassword) {
+ signInWithEmailAndPassword(auth, email, password)
+ .then(data => {console.log(data.user)
+
+ console.log('User signed in successfully!');
+ router.push('/browse');
+ })
+ // Navigate to the home screen or other desired screen
+ .catch(error => {
+ console.error(error);
+ // Display an error message to the user
+ });
+ }
+ };
+
+ return (
+ <div className='text-white flex flex-col items-center justify-center h-screen'>
+ <h1 className='text-2xl font-bold mb-6'>Login</h1>
+ <form className='w-64' >
+ <div className='mb-4'>
+ <label htmlFor='email' className='block font-medium mb-1'>Email:</label>
+ <input type="email" id='email' className='text-black w-full px-3 py-2 border rounded'/>
+ </div>
+ <div className='mb-4'>
+ <label htmlFor="password" className='block font-medium mb-1'>Password:</label>
+ <input type='password' id='password' className='text-black w-full px-3 py-2 border rounded'/>
+ </div>
+ <button type="submit" className='w-full py-2 bg-red-600 text-white font-medium rounded'>
+ Login
+ </button>
+ </form>
+
+ <div className="mt-4">
+ Don't have an account?{' '}
+ <Link href='../register' className="w-[128px] h-[128px] rounded-xl overflow-hidden bg-white">
+ <p>Create account</p>
+ </Link>
+ </div>
+
+ <div className="mt-2">
+ Forgot your password?{' '}
+ <Link href='../password-recovery' className="w-[128px] h-[128px] rounded-xl overflow-hidden bg-white">
+ <p>Recover</p>
+ </Link>
+ </div>
+ </div>
+ );
+}
diff --git a/app/layout.js b/app/layout.js new file mode 100644 index 0000000..45773fb --- /dev/null +++ b/app/layout.js @@ -0,0 +1,20 @@ +import '../src/styles/globals.css';
+import { Inter } from 'next/font/google';
+import { AuthProvider } from './components/AuthContext';
+
+const inter = Inter({
+ subsets: ['latin'],
+ variable: '--font-inter',
+});
+
+export default function RootLayout({ children }) {
+ return (
+ <html lang="en">
+ <AuthProvider>
+ <body className={`${inter.variable} font-sans bg-black h-screen`}>
+ {children}
+ </body>
+ </AuthProvider>
+ </html>
+ );
+}
\ No newline at end of file diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..35ed0aa --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,125 @@ +'use client';
+import { useState, useEffect, useContext } from 'react';
+import { signInWithEmailAndPassword } from 'firebase/auth';
+import { auth } from '../../services/firebase';
+import Link from 'next/link';
+import { useRouter } from 'next/navigation';
+import { AuthContext } from '../components/AuthContext'
+
+
+const Login = () => {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [isValidEmail, setIsValidEmail] = useState(false);
+ const [isValidPassword, setIsValidPassword] = useState(false);
+ const [isPasswordVisible, setIsPasswordVisible] = useState(true);
+ const router = useRouter()
+ const user = useContext(AuthContext);
+
+ useEffect(() => {
+ // Check if user is authenticated
+ if (!user) {
+ // Redirect or perform any necessary action
+ } else {
+ // User is authenticated, continue with desired logic
+ router.push('/profiles');
+ }
+ }, [user]);
+
+ useEffect(() => {
+ debounce(emailValidation());
+ }, [email]);
+
+ useEffect(() => {
+ debounce(passwordValidation());
+ }, [password]);
+
+ const emailValidation = () => {
+ const emailRegex = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
+ if (!email || emailRegex.test(email) === false) {
+ setIsValidEmail(false);
+ return false;
+ }
+ setIsValidEmail(true);
+ return true;
+ };
+
+ const passwordValidation = () => {
+ const passwordRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$/i;
+
+ if (!password || passwordRegex.test(password) === false) {
+ setIsValidPassword(false)
+ return false
+ }
+ setIsValidPassword(true)
+ return true
+ }
+
+ const debounce = fn => {
+ let id = null;
+
+ return (...args) => {
+ if (id) {
+ clearTimeout(id);
+ }
+ id = setTimeout(() => {
+ fn(...args);
+ id = null;
+ }, 300);
+ };
+ };
+
+ const loginUser = () => {
+ if (isValidEmail && isValidPassword) {
+ signInWithEmailAndPassword(auth, email, password)
+ .then(data => {console.log(data.user)
+
+ console.log('User signed in successfully!');
+ router.push('/profiles');
+
+ })
+ // Navigate to the home screen or other desired screen
+ .catch(error => {
+ console.error(error);
+ // Display an error message to the user
+ });
+ }
+ };
+
+ return (
+ <div className='text-white flex flex-col items-center justify-center h-screen'>
+ <h1 className='text-2xl font-bold mb-6'>Login</h1>
+ <form className='w-64' onSubmit={e => {
+ e.preventDefault();
+ loginUser();
+ }}>
+ <div className='mb-4'>
+ <label htmlFor='email' className='block font-medium mb-1'>Email:</label>
+ <input type="email" id='email' className='text-black w-full px-3 py-2 border rounded' value={email} onChange={e => setEmail(e.target.value)} />
+ </div>
+ <div className='mb-4'>
+ <label htmlFor="password" className='block font-medium mb-1'>Password:</label>
+ <input type='password' id='password' className='text-black w-full px-3 py-2 border rounded' value={password} onChange={e => setPassword(e.target.value)} />
+ </div>
+ <button type="submit" className='w-full py-2 bg-red-600 text-white font-medium rounded'>
+ Login
+ </button>
+ </form>
+
+ <div className="mt-4">
+ Don't have an account?{' '}
+ <Link href='../register' className="w-[128px] h-[128px] rounded-xl overflow-hidden bg-white">
+ <p>Create account</p>
+ </Link>
+ </div>
+
+ <div className="mt-2">
+ Forgot your password?{' '}
+ <Link href='../password-recovery' className="w-[128px] h-[128px] rounded-xl overflow-hidden bg-white">
+ <p>Recover</p>
+ </Link>
+ </div>
+ </div>
+ );
+}
+export default Login;
\ No newline at end of file diff --git a/app/password-recovery/page.tsx b/app/password-recovery/page.tsx new file mode 100644 index 0000000..6a56d7a --- /dev/null +++ b/app/password-recovery/page.tsx @@ -0,0 +1,44 @@ +'use client'
+import { link } from 'fs'
+import Link from 'next/link'
+import { AuthContext } from '../components/AuthContext'
+import { useContext, useEffect, useState } from 'react';
+
+// import NetflixLogo from '../../../public/images/netflix_logo.svg'
+
+export default function PasswordRecovery() {
+ const user = useContext(AuthContext);
+ const [email, setEmail] = useState('');
+
+ useEffect(() => {
+ // Check if user is authenticated
+ if (!user) {
+ // Redirect or perform any necessary action
+ } else {
+ // User is authenticated, continue with desired logic
+ setEmail(user.email)
+ }
+ }, [user]);
+
+ return (
+ <div className='text-white flex flex-col items-center justify-center h-screen'>
+ <h1 className='text-2xl font-bold mb-6'>Password Recovery</h1>
+ <form className='w-64'>
+ <div className='mb-4'>
+ <label htmlFor='email' className='block font-medium mb-1'>Email:</label>
+ <input type="email" id='email' value={email} className='w-full px-3 py-2 border rounded text-black' onChange={e => setEmail(e.target.value)}/>
+ </div>
+ <button className='w-full py-2 bg-red-600 text-white font-medium rounded'>
+ Recover
+ </button>
+ </form>
+
+ <div className="mt-4">
+ Do you have an account?{' '}
+ <Link href='../login' className="w-[128px] h-[128px] rounded-xl overflow-hidden bg-white">
+ <p>Login</p>
+ </Link>
+ </div>
+ </div>
+ );
+}
diff --git a/app/profiles/page.tsx b/app/profiles/page.tsx new file mode 100644 index 0000000..fca89a5 --- /dev/null +++ b/app/profiles/page.tsx @@ -0,0 +1,56 @@ +'use client' +import Link from "next/link" +import Image from "next/image" +import { AuthContext } from '../components/AuthContext' +import { useContext, useEffect } from "react"; +import { useRouter } from 'next/navigation'; + +export default function Profiles() { + + const user = useContext(AuthContext); + const router = useRouter(); + + useEffect(() => { + // Check if user is authenticated + if (!user) { + // Redirect or perform any necessary action + router.push('/login'); + } else { + // User is authenticated, continue with desired logic + } + }, [user]); + + return ( + <div className="bg-black w-screen h-screen flex flex-col items-center justify-center"> + <h1>Netflix</h1> + <p className="text-white text-[3.5vw]">Who's watching?</p> + <div className="flex flex-row gap-3 mt-6"> + { + USERS.map((user, index) => ( + <div key={index} className="flex flex-col items-center justify-center"> + <Link href={'/browse'} className="w-[128px] h-[128px] rounded-xl overflow-hidden bg-white"> + <Image src={user.avatar} width={128} height={128} alt=""/> + </Link> + <p className="text-gray-400 text-xs">{user.name}</p> + </div> + ))} + </div> + </div> + ) +} + +const USERS = [ + { + name:'Alberto', + avatar:'https://avatars.dicebear.com/api/male/124.svg' + }, + { + name:'Kids', + avatar:'https://avatars.dicebear.com/api/male/122.svg' + }, + { + name:'Add profile', + avatar:'https://avatars.dicebear.com/api/female/12.svg' + // icon:'...' + } +]
\ No newline at end of file diff --git a/app/register/page.tsx b/app/register/page.tsx new file mode 100644 index 0000000..45941fd --- /dev/null +++ b/app/register/page.tsx @@ -0,0 +1,123 @@ +'use client';
+import Link from 'next/link'
+import { useContext, useEffect, useState } from 'react';
+import { auth } from '../../services/firebase';
+import { createUserWithEmailAndPassword } from 'firebase/auth';
+import { useRouter } from 'next/navigation';
+import { AuthContext } from '../components/AuthContext'
+
+// import NetflixLogo from '../../../public/images/netflix_logo.svg'
+
+const Register = () => {
+
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [isValidEmail, setIsValidEmail] = useState(false);
+ const [isValidPassword, setIsValidPassword] = useState(false);
+ const [isPasswordVisible, setIsPasswordVisible] = useState(true);
+ const router = useRouter()
+ const user = useContext(AuthContext);
+
+
+ useEffect(() => {
+ // Check if user is authenticated
+ if (!user) {
+ // Redirect or perform any necessary action
+ } else {
+ // User is authenticated, continue with desired logic
+ router.push('/profiles');
+ }
+ }, [user]);
+
+ useEffect(() => {
+ debounce(emailValidation());
+ }, [email]);
+
+ useEffect(() => {
+ debounce(passwordValidation());
+ }, [password]);
+
+ const emailValidation = () => {
+ const emailRegex = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
+ if (!email || emailRegex.test(email) === false) {
+ setIsValidEmail(false);
+ return false;
+ }
+ setIsValidEmail(true);
+ return true;
+ };
+
+ const passwordValidation = () => {
+ const passwordRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$/i;
+
+
+ if (!password || passwordRegex.test(password) === false) {
+ setIsValidPassword(false)
+ return false
+ }
+ setIsValidPassword(true)
+ return true
+ }
+
+ // Utility FN ยท Mover a carpeta utils/utils.js
+ const debounce = fn => {
+ let id = null;
+
+ return (...args) => {
+ if (id) {
+ clearTimeout(id);
+ }
+ id = setTimeout(() => {
+ fn(...args);
+ id = null;
+ }, 300);
+ };
+ };
+
+
+ const registerUser = () => {
+ if (isValidEmail && isValidPassword) {
+ createUserWithEmailAndPassword(auth, email, password)
+ .then(() => {
+ console.log('User account created & signed in!');
+ // Navigate to the home screen or other desired screen
+ router.push('/browse');
+ })
+ .catch(error => {
+ console.error(error);
+ // Display an error message to the user
+ });
+ }
+};
+
+ return (
+ <div className='text-white flex flex-col items-center justify-center h-screen'>
+ <h1 className='text-2xl font-bold mb-6'>Register</h1>
+ <form className='w-64' onSubmit={e => {
+ e.preventDefault();
+ registerUser();
+ }}>
+ <div className='mb-4'>
+ <label htmlFor='email' className='block font-medium mb-1'>Email:</label>
+ <input type="email" id='email' className='text-black w-full px-3 py-2 border rounded' value={email} onChange={e => setEmail(e.target.value)} />
+ </div>
+ <div className='mb-4'>
+ <label htmlFor="password" className='block font-medium mb-1'>Password:</label>
+ <input type='password' id='password' className='text-black w-full px-3 py-2 border rounded' value={password} onChange={e => setPassword(e.target.value)} />
+ </div>
+ <button type="submit" className='w-full py-2 bg-red-600 text-white font-medium rounded'>
+ Register
+ </button>
+ </form>
+
+ <div className="mt-4">
+ Do you have an account?{' '}
+ <Link href='../login' className="w-[128px] h-[128px] rounded-xl overflow-hidden bg-white">
+ <p>Login</p>
+ </Link>
+ </div>
+ </div>
+ );
+}
+
+export default Register;
\ No newline at end of file |