├── .gitignore ├── README.md ├── components ├── Footer.jsx ├── NavBar.jsx ├── auth │ ├── AuthBtn.jsx │ └── FormInput.jsx ├── cart │ ├── CartItem.jsx │ └── NoItemCart.jsx ├── courses │ ├── commentMsg.jsx │ ├── detail │ │ ├── Banner.jsx │ │ ├── CourseContentItem.jsx │ │ ├── CourseContentList.jsx │ │ ├── CourseContentSec.jsx │ │ ├── CourseDetail.jsx │ │ ├── Description.jsx │ │ ├── FeedBack.jsx │ │ ├── WhatLearnt.jsx │ │ └── WhatLearntItem.jsx │ ├── list │ │ └── CourseListCard.jsx │ └── study │ │ ├── CommentArea.jsx │ │ ├── CommentBox.jsx │ │ ├── WatchArea.jsx │ │ ├── WatchList.jsx │ │ └── WatchListItem.jsx ├── index │ ├── CategoryCard.jsx │ ├── CategoryList.jsx │ ├── CompanyCard.jsx │ ├── CourseCard.jsx │ ├── CourseList.jsx │ ├── CourseSuggest.jsx │ ├── Header.jsx │ ├── TeachUdemy.jsx │ ├── TrustedCompanies.jsx │ └── courseSuggestChangeBtn.jsx ├── layouts │ ├── main.jsx │ └── watch.jsx └── user │ └── courseItem.jsx ├── config └── app.js ├── context ├── AuthContext.jsx └── CartContext.jsx ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── api │ ├── addComment.js │ ├── login.js │ ├── logout.js │ ├── payment.js │ ├── refresh_token.js │ ├── signup.js │ └── user.js ├── auth │ ├── login.js │ └── signup.js ├── cart │ └── index.jsx ├── course │ ├── author │ │ ├── [course_uuid] │ │ │ └── manage │ │ │ │ ├── delete.js │ │ │ │ └── update.js │ │ ├── course_add.js │ │ └── course_list.js │ ├── detail │ │ └── [course_uuid].js │ ├── sector │ │ └── [sector_uuid].js │ └── study │ │ └── [course_uuid].js ├── index.jsx ├── search │ └── [term].jsx └── user.jsx ├── public ├── assets │ ├── pexels-louis.jpg │ ├── udemy-b1.svg │ ├── udemy-b2.svg │ ├── udemy-b3.svg │ ├── udemy-b4.svg │ ├── udemy-b5.svg │ ├── udemy-red-white.svg │ ├── udemy1.jpg │ ├── udemy_logo-red.svg │ ├── udemy_logo.svg │ └── udemy_teacher.jpg └── favicon.ico ├── redux ├── slices │ └── AuthSlices.js └── store.js └── styles └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const Footer = () => { 4 | return ( 5 | <> 6 | 45 | 46 | 47 | 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /components/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext,useState } from 'react' 2 | import Link from 'next/link' 3 | import { useRouter } from 'next/router' 4 | import AuthContext from '../context/AuthContext' 5 | import CartContext from '../context/CartContext' 6 | 7 | 8 | export const NavBar = () => { 9 | 10 | const router=useRouter() 11 | 12 | const pushRoute=(route)=> { 13 | router.push(route) 14 | } 15 | 16 | const {user,logout}=useContext(AuthContext) 17 | 18 | const {cart} = useContext(CartContext) 19 | const [term,setTerm]=useState('') 20 | 21 | const handleChange=e=>{ 22 | setTerm(e.target.value) 23 | } 24 | 25 | const handleSubmit=e=>{ 26 | e.preventDefault() 27 | router.push('/search/'+term) 28 | setTerm('') 29 | } 30 | 31 | 32 | return ( 33 | 83 | ) 84 | } 85 | -------------------------------------------------------------------------------- /components/auth/AuthBtn.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function AuthBtn({action,disabled}) { 4 | return ( 5 | 8 | ) 9 | } 10 | 11 | export default AuthBtn 12 | -------------------------------------------------------------------------------- /components/auth/FormInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const FormInput = ({iconName,type,name,placeholder,setInput,inputVal}) => { 4 | return ( 5 |
6 |
7 | 8 |
9 | setInput(e.target.value)} className="w-9/12 md:w-10/12 block py-2 px-1 md:text-lg outline-none" type={type} name={name} /> 10 |
11 | ) 12 | } 13 | 14 | export default FormInput 15 | -------------------------------------------------------------------------------- /components/cart/CartItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function CartItem({detail,removeCart,removeCartDetail}) { 4 | // console.log("detail: ",detail) 5 | 6 | const handleRemove=()=>{ 7 | removeCart(detail.course_uuid) 8 | removeCartDetail(detail.course_uuid) 9 | } 10 | 11 | return ( 12 |
13 |
14 | course_card_img 15 |
16 | 17 |
18 | 19 |

20 | {detail.title.length > 60 ? detail.title.slice(0,59) +"..." : detail.title} 21 |

22 | 23 |
24 | By {detail.author.name} 25 |
26 | 27 |
28 | 29 |
30 |

31 | ${detail.price} 32 |

33 | 34 |
35 | 36 |
37 |

38 | 41 | 42 |

43 |
44 | 45 |
46 | ) 47 | } 48 | 49 | export default CartItem 50 | -------------------------------------------------------------------------------- /components/cart/NoItemCart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | 4 | function NoItemCart() { 5 | return ( 6 |
7 |
8 | 9 |
10 |

11 | Your cart is empty. Keep shopping to find a course! 12 |

13 | 14 | 17 | 18 | 19 |
20 | ) 21 | } 22 | 23 | export default NoItemCart 24 | -------------------------------------------------------------------------------- /components/courses/commentMsg.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function commentMsg({comment}) { 4 | const colors=['blue','indigo','red','yellow','green'] 5 | const color=Math.floor(Math.random() * 4) 6 | return ( 7 |
8 |
9 |
10 |

11 | {comment.user.name[0].toUpperCase()} 12 |

13 |
14 |
15 |
16 |

17 | {comment.user.name} 18 |

19 |

20 | {comment.message} 21 |

22 |
23 |
24 | ) 25 | } 26 | 27 | export default commentMsg 28 | -------------------------------------------------------------------------------- /components/courses/detail/Banner.jsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import React, { useContext } from 'react' 3 | import AuthContext from '../../../context/AuthContext' 4 | import CartContext from '../../../context/CartContext' 5 | import Link from 'next/link' 6 | 7 | 8 | function Banner({data}) { 9 | 10 | const {addCart,cart,removeCart} = useContext(CartContext) 11 | const router = useRouter() 12 | 13 | const {course_uuid} = router.query 14 | 15 | 16 | 17 | const {user}=useContext(AuthContext) 18 | 19 | const courses= user?.courses || [] 20 | 21 | 22 | 23 | 24 | const handleCarting=()=>{ 25 | 26 | if(cart.includes(course_uuid)){ 27 | 28 | removeCart(course_uuid) 29 | } else{ 30 | addCart(course_uuid) 31 | 32 | } 33 | } 34 | return ( 35 |
36 |

37 | {data.title} 38 |

39 |

40 | {data.description.length > 60 ? data.description.slice(0,59) +"..." : data.description} 41 |

42 |
43 | 46 | {data.rating} rating 47 | 48 | ({data.number_of_rating} ratings) {data.student_no} students 49 | 50 |
51 |

52 | Created by {data.author} 53 |

54 |

55 | ${data.price} 56 |

57 | 58 | {!courses.includes(course_uuid) ? 59 | : 62 | 63 | 64 | 67 | 68 | 69 | } 70 | 71 |
72 | ) 73 | } 74 | 75 | export default Banner 76 | -------------------------------------------------------------------------------- /components/courses/detail/CourseContentItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function CourseContentItem({data,setSrc}) { 4 | 5 | const handleClick=()=>{ 6 | if(setSrc){ 7 | setSrc(data.file) 8 | } 9 | 10 | } 11 | 12 | return ( 13 |
14 |
15 |
16 | 17 |
18 |
19 |

20 | 21 | {data.title} 22 |

23 |
24 |
25 |
26 |

27 | {data.length} 28 |

29 |
30 |
31 | 32 | ) 33 | } 34 | 35 | export default CourseContentItem 36 | -------------------------------------------------------------------------------- /components/courses/detail/CourseContentList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CourseContentItem from './CourseContentItem' 3 | 4 | function CourseContentList({hidden,data}) { 5 | 6 | return ( 7 |
8 | 9 | { 10 | data.map((episode,index)=>( 11 | 12 | 13 | )) 14 | } 15 | 16 |
17 | ) 18 | } 19 | 20 | export default CourseContentList 21 | -------------------------------------------------------------------------------- /components/courses/detail/CourseContentSec.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import CourseContentList from './CourseContentList' 3 | 4 | function CourseContentSec({section}) { 5 | const [hidden,setHidden] =useState(true) 6 | 7 | const changeHidden=()=>{ 8 | setHidden(!hidden) 9 | } 10 | 11 | 12 | return ( 13 | 14 |
15 |
16 | 17 |
18 | 19 |
20 | {hidden ? : 21 | } 22 |
23 |
24 |

25 | 26 | {section.section_title} 27 |

28 |
29 |
30 |
31 |
    32 |
  • {section.episodes.length} lecture{section.episodes.length>1 &&'s'}
  • 33 |
  • • {section.total_duration}
  • 34 |
35 |
36 |
37 | 38 |
44 | ) 45 | } 46 | 47 | export default CourseContentSec 48 | -------------------------------------------------------------------------------- /components/courses/detail/CourseDetail.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CourseContentSec from './CourseContentSec' 3 | 4 | function CourseDetail({sections,total_lectures,total_length}) { 5 | return ( 6 |
7 | 8 |
9 | 10 |

11 | Course Content 12 |

13 | 20 |
21 | { 22 | sections.map((section,index)=>( 23 | 24 | 25 | )) 26 | } 27 | 28 | 29 | 30 |
31 |
32 |
33 | ) 34 | } 35 | 36 | export default CourseDetail 37 | -------------------------------------------------------------------------------- /components/courses/detail/Description.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function Description({info}) { 4 | return ( 5 |
6 |
7 | 8 |

9 | Description 10 |

11 | 12 |
13 | {info} 14 |
15 |
16 |
17 | ) 18 | } 19 | 20 | export default Description 21 | -------------------------------------------------------------------------------- /components/courses/detail/FeedBack.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CommentMsg from '../commentMsg' 3 | 4 | function FeedBack({comments}) { 5 | return ( 6 |
7 |
8 |

9 | Feedbacks 10 |

11 | 12 | { 13 | comments.length? comments.map(comment=>( 14 | 15 | 16 | )) : 17 |

18 | No comments 19 |

20 | 21 | } 22 | 23 | 24 | 25 |
26 |
27 | ) 28 | } 29 | 30 | export default FeedBack 31 | -------------------------------------------------------------------------------- /components/courses/detail/WhatLearnt.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import WhatLearntItem from './WhatLearntItem' 3 | 4 | function WhatLearnt() { 5 | return ( 6 |
7 |
8 |

9 | What you'll learn 10 |

11 |
12 |
13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 |
29 | ) 30 | } 31 | 32 | export default WhatLearnt 33 | -------------------------------------------------------------------------------- /components/courses/detail/WhatLearntItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function WhatLearntItem({msg}) { 4 | return ( 5 |
6 |
7 | 8 |
9 |

10 | 11 | {msg} 12 |

13 |
14 | ) 15 | } 16 | 17 | export default WhatLearntItem 18 | -------------------------------------------------------------------------------- /components/courses/list/CourseListCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | 4 | function CourseListCard({data}) { 5 | return ( 6 | 7 | 8 |
9 |
10 | course_card_img 11 |
12 | 13 |
14 | 15 |

16 | {data.title.length > 60 ? data.title.slice(0,59) +"..." : data.title} 17 |

18 |

19 | {data.description} 20 |

21 |
22 | {data.author.name} 23 |
24 |

25 | {data.rating} ({data.student_no}) 26 |

27 |

28 | 22 total hours • {data.total_lectures} lectures 29 |

30 | 33 |
34 | 35 |
36 |

37 | ${data.price} 38 |

39 | 40 |
41 |
42 | 43 | ) 44 | } 45 | 46 | export default CourseListCard 47 | -------------------------------------------------------------------------------- /components/courses/study/CommentArea.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CommentMsg from '../commentMsg' 3 | import CommentBox from './CommentBox' 4 | 5 | function CommentArea({comments,setComment}) { 6 | 7 | 8 | 9 | return ( 10 |
11 |
12 |

13 | Comments 14 |

15 | 16 | 17 | { 18 | comments.map((comment,index) =>()) 19 | } 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 | ) 29 | } 30 | 31 | export default CommentArea 32 | -------------------------------------------------------------------------------- /components/courses/study/CommentBox.jsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import React, {useState,useContext} from 'react' 3 | import { toast } from 'react-toastify' 4 | import { NEXT_BACKEND_URI } from '../../../config/app' 5 | import AuthContext from '../../../context/AuthContext' 6 | 7 | 8 | function CommentBox({setComment,comments}) { 9 | 10 | const [message,setMessage]=useState('') 11 | 12 | const router = useRouter() 13 | const { course_uuid } = router.query 14 | 15 | 16 | const handleChange=(e)=>{ 17 | setMessage(e.target.value) 18 | } 19 | 20 | const {user:{name}}=useContext(AuthContext) 21 | 22 | const handleClick=async(e)=>{ 23 | 24 | e.preventDefault() 25 | 26 | const new_comment={ 27 | user:{ 28 | name 29 | }, 30 | message, 31 | 32 | } 33 | // TODO: post comment to API 34 | const res=await fetch(`${NEXT_BACKEND_URI}/addComment/`,{ 35 | method:'POST', 36 | body:JSON.stringify({data:{message},course_uuid}) 37 | }) 38 | 39 | // console.log(res.ok,(await res).status) 40 | if (!res.ok){ 41 | toast.error("could not add your comment") 42 | }else{ 43 | toast.success('comment added') 44 | setComment([...comments,new_comment]) 45 | setMessage('') 46 | } 47 | 48 | 49 | 50 | } 51 | 52 | 53 | return ( 54 |
55 |

56 | Leave A Comment 57 |

58 |
59 | 60 | 63 |
64 |
65 | ) 66 | } 67 | 68 | export default CommentBox 69 | -------------------------------------------------------------------------------- /components/courses/study/WatchArea.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState,useEffect } from 'react' 2 | import WatchList from './WatchList' 3 | 4 | function WatchArea({sections}) { 5 | const [src,setSrc] =useState(sections[0].episodes[0].file) 6 | 7 | 8 | return ( 9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 |

17 | Course Content 18 |

19 |
20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 | 28 |
29 | ) 30 | } 31 | 32 | export default WatchArea 33 | -------------------------------------------------------------------------------- /components/courses/study/WatchList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import WatchListItem from './WatchListItem' 3 | 4 | function WatchList({sections,setSrc}) { 5 | return ( 6 | <> 7 | 8 | { 9 | sections.map((section,index)=>( 10 | 11 | 12 | )) 13 | } 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | export default WatchList 22 | -------------------------------------------------------------------------------- /components/courses/study/WatchListItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import CourseContentItem from '../detail/CourseContentItem' 3 | 4 | 5 | 6 | function WatchListItem({section,setSrc}) { 7 | const [hidden, setHidden] = useState() 8 | const handleHidden=()=>{ 9 | setHidden(!hidden) 10 | } 11 | return ( 12 |
13 |
14 | 15 |
16 | 17 |
18 |

19 | 20 | {section.section_title} 21 |

22 |
23 |
24 | {hidden ? : 25 | } 26 |
27 |
28 | 29 |
30 | 31 | 38 |
39 | ) 40 | } 41 | 42 | export default WatchListItem 43 | -------------------------------------------------------------------------------- /components/index/CategoryCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | 4 | export const CategoryCard = ({data}) => { 5 | 6 | return ( 7 | 8 | 9 |
10 |
11 | sector_card_img 12 |
13 |
14 |

15 | {data.sector_name[0].toUpperCase()+data.sector_name.slice(1,data.sector_name.length)} 16 |

17 |
18 |
19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /components/index/CategoryList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CategoryCard } from './CategoryCard' 3 | 4 | const CategoryList = ({data}) => { 5 | 6 | const sectors=data.map(sector=>({sector_name:sector.sector_name, uuid:sector.sector_uuid,sector_image:sector.sector_image})) 7 | return ( 8 |
9 |

10 | Top categories 11 |

12 | 13 |
14 | 15 | {sectors.map(sector=> )} 16 | 17 |
18 |
19 | ) 20 | } 21 | 22 | export default CategoryList; 23 | -------------------------------------------------------------------------------- /components/index/CompanyCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const CompanyCard = ({src,alt,resize}) => { 4 | return ( 5 |
6 | {alt 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /components/index/CourseCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | 4 | export const CourseCard = ({data}) => { 5 | 6 | return ( 7 | 8 | 9 |
10 |
11 | course_card_img 12 |
13 | 14 |
15 | 16 |

17 | {/* Machine Learning A-Z™: Hands-On Python & R In Data... */} 18 | {data.title.length > 60 ? data.title.slice(0,59) +"..." : data.title} 19 |

20 |
21 | {/* OtchereDev */} 22 | By {data.author.name.length > 10 ? data.author.name.slice(0,9) +"..." : data.author.name} 23 |
24 |

25 | rating: {data.rating} ({data.student_no}) 26 |

27 |

28 | ${data.price} 29 |

30 | 33 |
34 |
35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /components/index/CourseList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CourseCard } from './CourseCard' 3 | 4 | const CourseList = ({data}) => { 5 | 6 | const courses=data.map(sector=>sector.featured_courses) 7 | 8 | 9 | return ( 10 |
11 |

12 | Students are viewing 13 |

14 |
15 | {courses.map(course_grp=>course_grp.map((course,index)=>))} 16 | 17 |
18 |
19 | ) 20 | } 21 | 22 | export default CourseList; 23 | -------------------------------------------------------------------------------- /components/index/CourseSuggest.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import {CourseCard} from './CourseCard' 3 | import { CourseSuggestChangeBtn } from './courseSuggestChangeBtn' 4 | 5 | const CourseSuggest = ({data}) => { 6 | 7 | const [suggest,setSuggest]=useState(data[0].sector_name) 8 | // console.log(suggest,data.find(sector=>sector.sector_name==suggest).featured_courses) 9 | const [suggestedCourse,setSuggestedCourse]=useState(data.find(sector=>sector.sector_name==suggest).featured_courses) 10 | 11 | useEffect(()=>{ 12 | setSuggestedCourse(data.find(sector=>sector.sector_name==suggest).featured_courses) 13 | },[suggest]) 14 | 15 | 16 | 17 | return ( 18 |
19 |

20 | The world's largest selection of courses 21 |

22 |

23 | Choose from 155,000 online video courses with new additions published every month 24 |

25 | 26 |
27 |
28 | {data.map(sector=>( 29 | 30 | 31 | ))} 32 | 33 | 34 |
35 |
36 |
37 |
38 |

39 | Lead data-driven decisions with Data Science 40 |

41 |

42 | Data science is everywhere. Better data science practices are allowing corporations to cut unnecessary costs, automate computing, and analyze markets. Essentially, data science is the key to getting ahead in a competitive global climate. 43 |

44 | 47 |
48 |
49 | 50 |
51 | sector_image 52 |
53 |
54 |
55 |
56 | { 57 | suggestedCourse.length? suggestedCourse.map((course,index)=>()) : 58 | 59 | } 60 |
61 |
62 |
63 | 64 | 65 |
66 | ) 67 | } 68 | 69 | export default CourseSuggest -------------------------------------------------------------------------------- /components/index/Header.jsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import React, { useState } from 'react' 3 | 4 | 5 | const Header = () => { 6 | const [term,setTerm]=useState('') 7 | const router =useRouter() 8 | const handleSubmit=e=>{ 9 | e.preventDefault() 10 | router.push(`/search/${term}`) 11 | 12 | } 13 | return ( 14 |
15 |
16 | background 17 |
18 |
19 |

20 | Dream Up 21 |

22 |

23 | Ambition accepted. Learn the latest skills to reach your professional goals. 24 |

25 |
26 | setTerm(e.target.value)} type="text" placeholder="What fo you want to learn ?" className="outline-none bg-transparent w-10/12 py-2" name="search_box"/> 27 |
28 | 29 |
30 |
31 |
32 | 33 |
34 | ) 35 | } 36 | 37 | export default Header 38 | -------------------------------------------------------------------------------- /components/index/TeachUdemy.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const TeachUdemy = () => { 4 | return ( 5 |
6 |
7 |
8 | 9 |
10 | 11 | teacher_image 12 |
13 |

14 | Become an instructor 15 |

16 |

17 | Top instructors from around the world teach millions of students on Udemy. We provide the tools and skills to teach what you love. 18 |

19 | 20 | 23 |
24 |
25 |
26 |
27 |
28 | ) 29 | } 30 | 31 | export default TeachUdemy; 32 | -------------------------------------------------------------------------------- /components/index/TrustedCompanies.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CompanyCard } from './CompanyCard' 3 | 4 | const TrustedCompanies = () => { 5 | return ( 6 |
7 |

8 | Trusted by companies of all sizes 9 |

10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | ) 26 | } 27 | 28 | export default TrustedCompanies; 29 | -------------------------------------------------------------------------------- /components/index/courseSuggestChangeBtn.jsx: -------------------------------------------------------------------------------- 1 | export const CourseSuggestChangeBtn = ({name,setSuggest}) => { 2 | return ( 3 | 4 | 5 | 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /components/layouts/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Footer } from '../Footer' 3 | import { NavBar } from '../NavBar' 4 | 5 | import { ToastContainer } from 'react-toastify'; 6 | import 'react-toastify/dist/ReactToastify.css'; 7 | 8 | const main = ({children}) => { 9 | 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 | {children} 17 | 18 |
19 |
20 | ) 21 | } 22 | 23 | export default main 24 | -------------------------------------------------------------------------------- /components/layouts/watch.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Footer } from '../Footer' 3 | import Link from 'next/link' 4 | 5 | import { ToastContainer } from 'react-toastify'; 6 | import 'react-toastify/dist/ReactToastify.css'; 7 | 8 | const main = ({children,title}) => { 9 | 10 | return ( 11 |
12 | 25 | 26 | 27 | 28 | {children} 29 | 30 |
31 |
32 | ) 33 | } 34 | 35 | export default main 36 | -------------------------------------------------------------------------------- /components/user/courseItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | 4 | function CourseItem({course}) { 5 | return ( 6 | 7 | 8 |
9 |
10 | course_card_img 11 |
12 | 13 |
14 | 15 |

16 | {course.title} 17 |

18 | 19 |
20 | {course.author.name} 21 |
22 | 23 |
24 | 25 |
26 | 27 | ) 28 | } 29 | 30 | export default CourseItem 31 | -------------------------------------------------------------------------------- /config/app.js: -------------------------------------------------------------------------------- 1 | // export const BACKEND_URI=process.env.BACKEND_URI 2 | // export const NEXT_BACKEND_URI=process.env.NEXT_BACKEND_URI 3 | 4 | export const BACKEND_URI="http://localhost:8000" 5 | export const NEXT_BACKEND_URI='http://localhost:3000/api' -------------------------------------------------------------------------------- /context/AuthContext.jsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { useState, createContext, useEffect } from "react"; 3 | import {NEXT_BACKEND_URI} from '../config/app' 4 | import { useSelector, useDispatch } from 'react-redux' 5 | import {addUser,removeUser} from '../redux/slices/AuthSlices' 6 | 7 | 8 | const AuthContext = createContext() 9 | 10 | export const AuthContextProvider=({children})=>{ 11 | const user=useSelector(state=>state.auth.user) 12 | const [authError,setAuthError]=useState(null) 13 | const [authReady,setAuthReady]=useState(false) 14 | 15 | const dispatch=useDispatch() 16 | 17 | 18 | 19 | 20 | 21 | 22 | const router = useRouter() 23 | 24 | useEffect(()=>checkUserLoggedIn(),[]) 25 | 26 | const login=async({email,password})=>{ 27 | const res = await fetch(`${NEXT_BACKEND_URI}/login`,{ 28 | 29 | method:"POST", 30 | headers:{ 31 | "Content-type":"application/json" 32 | }, 33 | body:JSON.stringify({email,password}) 34 | }) 35 | 36 | const data= await res.json() 37 | 38 | 39 | if (res.ok){ 40 | 41 | // setUser(data.user) 42 | 43 | await checkUserLoggedIn() 44 | 45 | router.push('/') 46 | 47 | }else{ 48 | 49 | setAuthError(data.detail) 50 | 51 | } 52 | 53 | 54 | } 55 | 56 | const signup=async({email,password,name})=>{ 57 | const res = await fetch(`${NEXT_BACKEND_URI}/signup`,{ 58 | 59 | method:"POST", 60 | headers:{ 61 | "Content-type":"application/json" 62 | }, 63 | body:JSON.stringify({email,password,name}) 64 | }) 65 | 66 | const data= await res.json() 67 | 68 | if (res.ok){ 69 | 70 | // setUser(data) 71 | 72 | await login({email,password}) 73 | 74 | 75 | 76 | }else{ 77 | if (data.name){ 78 | 79 | setAuthError(data.name[0]) 80 | }else if(data.email){ 81 | setAuthError(data.email[0]) 82 | }else{ 83 | setAuthError(data.password.join('\n')) 84 | } 85 | 86 | } 87 | 88 | } 89 | 90 | const logout=async()=>{ 91 | const res= await fetch(`${NEXT_BACKEND_URI}/logout`,{ 92 | method:"POST" 93 | }) 94 | 95 | if (res.ok){ 96 | // setUser(null) 97 | // push user if necessary 98 | dispatch(removeUser()) 99 | } 100 | } 101 | 102 | const checkUserLoggedIn=async()=>{ 103 | 104 | 105 | 106 | const res = await fetch(`${NEXT_BACKEND_URI}/user`) 107 | 108 | 109 | 110 | 111 | 112 | if (res.ok){ 113 | const data= await res.json() 114 | dispatch(addUser(data.user)) 115 | // setUser(data.user) 116 | 117 | }else{ 118 | // setUser(null) 119 | dispatch(addUser(null)) 120 | 121 | 122 | } 123 | 124 | setAuthReady(true) 125 | 126 | return 127 | 128 | 129 | } 130 | 131 | const clearUser=()=>{ 132 | 133 | dispatch(removeUser()) 134 | } 135 | 136 | return ( 137 | 138 | {authReady && children} 139 | 140 | ) 141 | } 142 | 143 | 144 | 145 | export default AuthContext -------------------------------------------------------------------------------- /context/CartContext.jsx: -------------------------------------------------------------------------------- 1 | 2 | import { useState, createContext, useEffect,useContext } from "react"; 3 | import { toast } from "react-toastify"; 4 | import AuthContext from "./AuthContext"; 5 | 6 | 7 | const CartContext = createContext() 8 | 9 | export const CartContextProvider=({children})=>{ 10 | 11 | const {user}=useContext(AuthContext) 12 | 13 | const initCart= localStorage.getItem('cart') ? JSON.parse(localStorage.getItem('cart')):[] 14 | const [cart,setCart]=useState([...initCart.filter(item=>{ 15 | 16 | if(user){ 17 | return !user.courses.includes(item) 18 | }else{ 19 | return true 20 | } 21 | })]) 22 | 23 | localStorage.setItem('cart',JSON.stringify(cart)) 24 | 25 | const addCart=(uuid)=>{ 26 | if(!cart.includes(uuid)){ 27 | setCart([...cart,uuid]) 28 | localStorage.setItem('cart',JSON.stringify([...cart,uuid])) 29 | toast.info('1 course added to cart') 30 | } 31 | } 32 | 33 | const removeCart=(uuid)=>{ 34 | const index = cart.findIndex(item=>item===uuid) 35 | 36 | if(index > -1){ 37 | const currentCart=[...cart] 38 | currentCart.splice(index,1) 39 | setCart(currentCart) 40 | localStorage.setItem('cart',JSON.stringify(currentCart)) 41 | toast.info('1 course removed from cart') 42 | } 43 | } 44 | 45 | const clearCart=()=>{ 46 | setCart([]) 47 | localStorage.setItem('cart',JSON.stringify([])) 48 | } 49 | 50 | 51 | 52 | 53 | 54 | return ( 55 | 56 | {children} 57 | 58 | ) 59 | } 60 | 61 | 62 | 63 | export default CartContext -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "udemy_frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@reduxjs/toolkit": "^1.6.0", 13 | "cookie": "^0.4.1", 14 | "next": "11.0.1", 15 | "react": "17.0.2", 16 | "react-dom": "17.0.2", 17 | "react-redux": "^7.2.4", 18 | "react-toastify": "^7.0.4" 19 | }, 20 | "devDependencies": { 21 | "eslint": "7.29.0", 22 | "eslint-config-next": "11.0.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/style.css' 2 | import {AuthContextProvider} from '../context/AuthContext' 3 | import {useEffect} from 'react' 4 | 5 | import {CartContextProvider} from '../context/CartContext' 6 | 7 | import { store } from '../redux/store' 8 | import { Provider } from 'react-redux' 9 | 10 | 11 | function MyApp({ Component, pageProps }) { 12 | 13 | useEffect(() => { 14 | const script1 = document.createElement('script'); 15 | 16 | script1.src = "https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"; 17 | script1.type = "module"; 18 | 19 | document.body.appendChild(script1); 20 | 21 | const script = document.createElement('script'); 22 | 23 | script.src = "https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"; 24 | script.noModule = true; 25 | 26 | document.body.appendChild(script); 27 | 28 | return () => { 29 | document.body.removeChild(script1); 30 | document.body.removeChild(script); 31 | } 32 | }, []); 33 | 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ) 49 | 50 | 51 | } 52 | 53 | 54 | export default MyApp 55 | -------------------------------------------------------------------------------- /pages/api/addComment.js: -------------------------------------------------------------------------------- 1 | import {BACKEND_URI} from '../../config/app' 2 | import cookie from 'cookie' 3 | import refreshToken from "./refresh_token" 4 | 5 | export default async(req,res)=>{ 6 | if (req.method === "POST"){ 7 | 8 | if (!req.headers.cookie){ 9 | 10 | res.status(403).json({"message":"Not authorized"}) 11 | return 12 | } 13 | 14 | let {refresh_token}=cookie.parse(req.headers.cookie) 15 | 16 | if (!refresh_token){ 17 | 18 | res.status(403).json({"message":"Not authorized"}) 19 | return 20 | } 21 | 22 | 23 | let {access_token}=cookie.parse(req.headers.cookie) 24 | 25 | 26 | if (!access_token){ 27 | const refreshRes= await refreshToken(req,res) 28 | 29 | 30 | if(refreshRes){ 31 | access_token=refreshRes 32 | }else{ 33 | 34 | res.status(403).json({"message":"Not authorized"}) 35 | return 36 | } 37 | 38 | 39 | 40 | } 41 | 42 | const body=JSON.parse(req.body) 43 | 44 | 45 | 46 | 47 | let resAPI = await fetch(`${BACKEND_URI}/courses/comment/${body.course_uuid}/`,{ 48 | method:"POST", 49 | headers:{ 50 | "Content-type":"application/json", 51 | "Authorization": `Token ${access_token}` 52 | }, 53 | body:JSON.stringify(body.data) 54 | 55 | }) 56 | 57 | 58 | 59 | 60 | if (resAPI.ok){ 61 | 62 | 63 | 64 | res.status(200).json({}) 65 | 66 | 67 | }else{ 68 | // send error message 69 | res.status(403).json({}) 70 | return 71 | } 72 | 73 | }else{ 74 | res.setHeader("Allow",["GET"]) 75 | res.status(403).json({"message":`Method ${req.method} not allowed`}) 76 | return 77 | } 78 | } -------------------------------------------------------------------------------- /pages/api/login.js: -------------------------------------------------------------------------------- 1 | import {BACKEND_URI} from '../../config/app' 2 | import cookie from 'cookie' 3 | 4 | export default async(req,res)=>{ 5 | if (req.method === "POST"){ 6 | 7 | const {email,password}=req.body 8 | 9 | const resAPI = await fetch(`${BACKEND_URI}/auth/jwt/create/`,{ 10 | 11 | method:"POST", 12 | headers:{ 13 | "Content-type":"application/json" 14 | }, 15 | body:JSON.stringify({email,password}) 16 | }) 17 | 18 | const data= await resAPI.json() 19 | 20 | 21 | if (resAPI.ok){ 22 | // set auth cookie 23 | res.setHeader("Set-Cookie",[cookie.serialize("refresh_token",data.refresh,{ 24 | httpOnly:true, 25 | secure: process.env.NODE_ENV !== "development", 26 | maxAge: 60 * 60 * 24, 27 | sameSite:"strict", 28 | path: '/' 29 | }), 30 | cookie.serialize("access_token",data.access,{ 31 | httpOnly:true, 32 | secure: process.env.NODE_ENV !== "development", 33 | maxAge: 60, 34 | sameSite:"strict", 35 | path: '/' 36 | })]) 37 | 38 | 39 | // send success in response 40 | res.status(200).json({}) 41 | 42 | }else{ 43 | // send error message 44 | 45 | res.status(401).json(data) 46 | } 47 | 48 | }else{ 49 | res.setHeader("Allow",["POST"]) 50 | res.status(403).json({"message":`Method ${req.method} not allowed`}) 51 | } 52 | } -------------------------------------------------------------------------------- /pages/api/logout.js: -------------------------------------------------------------------------------- 1 | 2 | import cookie from 'cookie' 3 | 4 | export default async(req,res)=>{ 5 | if (req.method === "POST"){ 6 | 7 | // remove auth cookies 8 | res.setHeader("Set-Cookie",[cookie.serialize("refresh_token","",{ 9 | httpOnly:true, 10 | secure: process.env.NODE_ENV !== "development", 11 | expires: new Date(0), 12 | sameSite:"strict", 13 | path: '/' 14 | },cookie.serialize("access_token","",{ 15 | httpOnly:true, 16 | secure: process.env.NODE_ENV !== "development", 17 | expires: new Date(0), 18 | sameSite:"strict", 19 | path: '/' 20 | }))]) 21 | 22 | res.status(204).json() 23 | 24 | 25 | 26 | }else{ 27 | res.setHeader("Allow",["POST"]) 28 | res.status(403).json({"message":`Method ${req.method} not allowed`}) 29 | } 30 | } -------------------------------------------------------------------------------- /pages/api/payment.js: -------------------------------------------------------------------------------- 1 | import {BACKEND_URI} from '../../config/app' 2 | import cookie from 'cookie' 3 | import refreshToken from "./refresh_token" 4 | 5 | export default async(req,res)=>{ 6 | if (req.method === "POST"){ 7 | 8 | console.log(req.body) 9 | 10 | if(!req.body||!req.body.cart||!req.body.cart.length){ 11 | 12 | res.status(400).json({"message":"Cart must contain atleast one item"}) 13 | return 14 | } 15 | 16 | if (!req.headers.cookie){ 17 | 18 | res.status(403).json({"message":"Not authorized"}) 19 | return 20 | } 21 | 22 | let {refresh_token}=cookie.parse(req.headers.cookie) 23 | 24 | if (!refresh_token){ 25 | 26 | res.status(403).json({"message":"Not authorized"}) 27 | return 28 | } 29 | 30 | 31 | let {access_token}=cookie.parse(req.headers.cookie) 32 | 33 | 34 | if (!access_token){ 35 | const refreshRes= await refreshToken(req,res) 36 | 37 | 38 | if(refreshRes){ 39 | access_token=refreshRes 40 | }else{ 41 | 42 | res.status(403).json({"message":"Not authorized"}) 43 | return 44 | } 45 | 46 | 47 | 48 | } 49 | 50 | 51 | const resAPI=await fetch(`${BACKEND_URI}/payments/`,{ 52 | method:"POST", 53 | headers:{ 54 | Authorization:`Token ${access_token}`, 55 | "Content-Type":"application/json" 56 | }, 57 | body:JSON.stringify(req.body.cart) 58 | }) 59 | 60 | if (resAPI.ok){ 61 | const data=await resAPI.json() 62 | // console.log(data) 63 | 64 | res.status(200).json({url:data.url}) 65 | }else{ 66 | res.status(400).json({}) 67 | } 68 | 69 | 70 | 71 | 72 | 73 | }else{ 74 | res.setHeader("Allow",["POST"]) 75 | res.status(403).json({"message":`Method ${req.method} not allowed`}) 76 | } 77 | } -------------------------------------------------------------------------------- /pages/api/refresh_token.js: -------------------------------------------------------------------------------- 1 | import {BACKEND_URI} from '../../config/app' 2 | import cookie from 'cookie' 3 | 4 | export default async(req,res)=>{ 5 | 6 | if (req.method === "GET" || req.url=='/api/payment'|| req.url=='/api/addComment'){ 7 | 8 | if (!req.headers.cookie){ 9 | 10 | return 11 | } 12 | 13 | const {refresh_token}=cookie.parse(req.headers.cookie) 14 | 15 | 16 | if (!refresh_token){ 17 | 18 | return 19 | } 20 | 21 | const resAPI = await fetch(`${BACKEND_URI}/auth/jwt/refresh/`,{ 22 | 23 | method:"POST", 24 | headers:{ 25 | "Content-type":"application/json", 26 | }, 27 | body:JSON.stringify({"refresh":refresh_token}) 28 | 29 | }) 30 | 31 | 32 | if (resAPI.ok){ 33 | const data= await resAPI.json() 34 | 35 | // set access cookie 36 | res.setHeader("Set-Cookie",cookie.serialize("access_token",data.access,{ 37 | httpOnly:true, 38 | secure: process.env.NODE_ENV !== "development", 39 | maxAge: 60, 40 | sameSite:"strict", 41 | path: '/' 42 | })) 43 | 44 | return data.access 45 | 46 | 47 | }else{ 48 | 49 | return 50 | } 51 | 52 | }else{ 53 | 54 | return 55 | } 56 | } -------------------------------------------------------------------------------- /pages/api/signup.js: -------------------------------------------------------------------------------- 1 | import {BACKEND_URI} from '../../config/app' 2 | 3 | 4 | export default async(req,res)=>{ 5 | if (req.method === "POST"){ 6 | 7 | const {email,password,name}=req.body 8 | 9 | const resAPI = await fetch(`${BACKEND_URI}/auth/users/`,{ 10 | 11 | method:"POST", 12 | headers:{ 13 | "Content-type":"application/json" 14 | }, 15 | body:JSON.stringify({email,password,name}) 16 | }) 17 | 18 | const data= await resAPI.json() 19 | 20 | 21 | if (resAPI.ok){ 22 | 23 | res.status(200).json(data) 24 | return 25 | 26 | }else{ 27 | // send error message 28 | res.status(401).json(data) 29 | return 30 | } 31 | 32 | }else{ 33 | res.setHeader("Allow",["POST"]) 34 | res.status(403).json({"message":`Method ${req.method} not allowed`}) 35 | } 36 | } -------------------------------------------------------------------------------- /pages/api/user.js: -------------------------------------------------------------------------------- 1 | import {BACKEND_URI} from '../../config/app' 2 | import cookie from 'cookie' 3 | import refreshToken from "./refresh_token" 4 | 5 | export default async(req,res)=>{ 6 | //console.log('running') 7 | if (req.method === "GET"){ 8 | //console.log(1) 9 | if (!req.headers.cookie){ 10 | //console.log(2) 11 | res.status(403).json({"message":"Not authorized"}) 12 | return 13 | } 14 | 15 | let {refresh_token}=cookie.parse(req.headers.cookie) 16 | 17 | if (!refresh_token){ 18 | //console.log(3) 19 | res.status(403).json({"message":"Not authorized"}) 20 | return 21 | } 22 | 23 | 24 | let {access_token}=cookie.parse(req.headers.cookie) 25 | 26 | 27 | if (!access_token){ 28 | const refreshRes= await refreshToken(req,res) 29 | //console.log(4) 30 | 31 | if(refreshRes){ 32 | access_token=refreshRes 33 | }else{ 34 | 35 | res.status(403).json({"message":"Not authorized"}) 36 | return 37 | } 38 | 39 | 40 | 41 | } 42 | 43 | 44 | 45 | let resAPI = await fetch(`${BACKEND_URI}/auth/users/me/`,{ 46 | method:"GET", 47 | headers:{ 48 | "Content-type":"application/json", 49 | "Authorization": `Token ${access_token}` 50 | }, 51 | 52 | }) 53 | //console.log(5) 54 | 55 | if (resAPI.ok){ 56 | //console.log(6) 57 | const user= await resAPI.json() 58 | 59 | // send user in response 60 | res.status(200).json({user}) 61 | return user 62 | 63 | }else{ 64 | // send error message 65 | res.status(403).json({}) 66 | return 67 | } 68 | 69 | }else{ 70 | res.setHeader("Allow",["GET"]) 71 | res.status(403).json({"message":`Method ${req.method} not allowed`}) 72 | return 73 | } 74 | } -------------------------------------------------------------------------------- /pages/auth/login.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import MainLayout from '../../components/layouts/main' 3 | import FormInput from '../../components/auth/FormInput' 4 | import AuthBtn from '../../components/auth/AuthBtn' 5 | import Link from 'next/link' 6 | import AuthContext from '../../context/AuthContext' 7 | import { toast } from 'react-toastify'; 8 | import cookie from 'cookie' 9 | 10 | const login = () => { 11 | 12 | const [email,setEmail]= useState('') 13 | const [password,setPassword]=useState('') 14 | const [authReady,setAuthReady]=useState(false) 15 | 16 | const {login,authError,clearUser}= useContext(AuthContext) 17 | 18 | useEffect(()=>clearUser(),[]) 19 | 20 | const handleSubmit=async(e)=>{ 21 | e.preventDefault(); 22 | 23 | setAuthReady(true) 24 | await login({email,password}) 25 | setAuthReady(false) 26 | 27 | 28 | } 29 | 30 | useEffect(()=>authError && toast.error(authError),[authError]) 31 | 32 | return ( 33 | 34 | 35 |
36 |
37 |

38 | Log In and Start Learning! 39 |

40 | 41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |

52 | Do not have an account? Sign Up 53 |

54 | 55 | 56 | 57 |
58 |
59 |
60 | ) 61 | } 62 | 63 | export const getServerSideProps=async({req,res})=>{ 64 | 65 | if (req.headers.cookie){ 66 | 67 | let cookies=cookie.parse(req.headers.cookie) 68 | 69 | if (cookies && cookies.refresh_token){ 70 | 71 | 72 | return { 73 | redirect:{ 74 | destination:'/', 75 | permanent:false 76 | } 77 | } 78 | } 79 | } 80 | 81 | 82 | return { 83 | props:{} 84 | } 85 | 86 | } 87 | 88 | export default login 89 | -------------------------------------------------------------------------------- /pages/auth/signup.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import MainLayout from '../../components/layouts/main' 3 | import FormInput from '../../components/auth/FormInput' 4 | import AuthBtn from '../../components/auth/AuthBtn' 5 | import Link from 'next/link' 6 | import AuthContext from '../../context/AuthContext' 7 | import { toast } from 'react-toastify' 8 | import cookie from 'cookie' 9 | 10 | const signup = () => { 11 | 12 | const {signup,authError,clearUser}= useContext(AuthContext) 13 | 14 | useEffect(()=>clearUser(),[]) 15 | 16 | const [name,setName]=useState('') 17 | const [email,setEmail]=useState('') 18 | const [password,setPassword]=useState('') 19 | const [authReady,setAuthReady]=useState(false) 20 | 21 | useEffect(()=>authError && toast.error(authError),[authError]) 22 | 23 | 24 | const handleSubmit=async(e)=>{ 25 | e.preventDefault() 26 | setAuthReady(true) 27 | await signup({name,email,password}) 28 | setAuthReady(false) 29 | } 30 | 31 | return ( 32 | 33 |
34 |
35 |

36 | Sign Up and Start Learning! 37 |

38 | 39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |

49 | By signing up, you agree to our Terms of Use and Privacy Policy. 50 |

51 | 52 |

53 | Already have an account? Log In 54 |

55 | 56 | 57 | 58 |
59 |
60 |
61 | ) 62 | } 63 | 64 | export const getServerSideProps=async({req,res})=>{ 65 | 66 | if(req.headers.cookie){ 67 | 68 | let cookies=cookie.parse(req.headers.cookie) 69 | 70 | if (cookies && cookies.refresh_token){ 71 | 72 | 73 | return { 74 | redirect:{ 75 | destination:'/', 76 | permanent:false 77 | } 78 | } 79 | } 80 | } 81 | 82 | 83 | return { 84 | props:{} 85 | } 86 | 87 | 88 | 89 | } 90 | 91 | 92 | export default signup 93 | -------------------------------------------------------------------------------- /pages/cart/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import MainLayout from '../../components/layouts/main' 3 | import CartItem from '../../components/cart/CartItem' 4 | import NoItemCart from '../../components/cart/NoItemCart' 5 | import CartContext from '../../context/CartContext' 6 | import { BACKEND_URI, NEXT_BACKEND_URI } from '../../config/app' 7 | import { useRouter } from 'next/router' 8 | import { toast } from 'react-toastify' 9 | 10 | function CartIndex() { 11 | // const [cartSize,setCartSize]=useState(0) 12 | 13 | const {cart,removeCart,clearCart} = useContext(CartContext) 14 | const [cartTotal,setCartTotal]=useState() 15 | const [cartReady,setCartReady] = useState(false) 16 | const [requestingPayment, setRequestingPayment] = useState(false) 17 | 18 | const [cartDetails,setCartDetails]= useState([]) 19 | 20 | const router = useRouter() 21 | 22 | const handleCheckout=async()=>{ 23 | if(cart.length){ 24 | 25 | setRequestingPayment(true) 26 | const res= await fetch(`${NEXT_BACKEND_URI}/payment/`,{ 27 | method:'POST', 28 | headers:{ 29 | "Content-Type":"application/json", 30 | 31 | }, 32 | body:JSON.stringify({cart}) 33 | 34 | }) 35 | if (res.ok){ 36 | 37 | const data = await res.json() 38 | toast.success("Redirecting you to payment page...") 39 | window.location=data.url 40 | 41 | 42 | } 43 | if(res.status==403){ 44 | setRequestingPayment(false) 45 | toast.error("Please login to proceed!!") 46 | await router.push('/auth/login') 47 | } 48 | if(!res.ok){ 49 | setRequestingPayment(false) 50 | toast.error("Sorry there was an error!!") 51 | } 52 | } 53 | } 54 | 55 | useEffect(async()=>{ 56 | if(cart.length >0){ 57 | 58 | const res = await fetch(`${BACKEND_URI}/courses/cart/`,{ 59 | method:'POST', 60 | headers:{ 61 | 'Content-type':'application/json' 62 | }, 63 | body:JSON.stringify({cart}), 64 | 65 | 66 | }) 67 | 68 | 69 | 70 | if(res.ok){ 71 | 72 | const details = await res.json() 73 | setCartTotal(details.cart_total) 74 | setCartDetails([...details.cart_detail]) 75 | setCartReady(true) 76 | 77 | } 78 | 79 | 80 | } 81 | 82 | },[]) 83 | 84 | const removeCartDetail=(uuid)=>{ 85 | 86 | const index = cartDetails.findIndex(detail=>detail.course_uuid===uuid) 87 | 88 | if(index > -1){ 89 | 90 | const currentCart=[...cartDetails] 91 | 92 | 93 | 94 | currentCart.splice(index,1) 95 | 96 | 97 | 98 | setCartDetails([...currentCart]) 99 | 100 | 101 | } 102 | 103 | } 104 | 105 | return ( 106 | 107 |
108 |

109 | Shopping Cart 110 |

111 |
112 | 113 |
114 |
115 |

116 | {cart.length} Course in Cart 117 |

118 | 119 | {cart.length > 0 && cartReady ? ( 120 | <> 121 | { 122 | cartDetails.length ? cart.map(item=>{ 123 | 124 | const courseItem=cartDetails.find(detail=>item==detail.course_uuid) 125 | 126 | return () 127 | }):'' 128 | } 129 | 130 | 131 | ) : 132 | 133 | 134 | } 135 | 136 | 137 | 138 | 139 |
140 | {cart.length > 0 &&
141 |

142 | Total: 143 |

144 |

145 | ${cartTotal} 146 |

147 | 150 | 153 |
} 154 | 155 | 156 |
157 | 158 |
159 | ) 160 | } 161 | 162 | export default CartIndex 163 | -------------------------------------------------------------------------------- /pages/course/author/[course_uuid]/manage/delete.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const deleteCourse = () => { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | 11 | export default deleteCourse 12 | -------------------------------------------------------------------------------- /pages/course/author/[course_uuid]/manage/update.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const update = () => { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | 11 | export default update 12 | -------------------------------------------------------------------------------- /pages/course/author/course_add.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const course_add = () => { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | 11 | export default course_add 12 | -------------------------------------------------------------------------------- /pages/course/author/course_list.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const course_list = () => { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | 11 | export default course_list 12 | -------------------------------------------------------------------------------- /pages/course/detail/[course_uuid].js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainLayout from '../../../components/layouts/main' 3 | import Banner from '../../../components/courses/detail/Banner' 4 | import WhatLearnt from '../../../components/courses/detail/WhatLearnt' 5 | import CourseDetail from '../../../components/courses/detail/CourseDetail' 6 | import Description from '../../../components/courses/detail/Description' 7 | import FeedBack from '../../../components/courses/detail/FeedBack' 8 | import { BACKEND_URI } from '../../../config/app' 9 | 10 | 11 | const course_uuid = ({data}) => { 12 | 13 | const banner_data={title:data.title, 14 | description:data.description, 15 | rating:data.student_rating, 16 | number_of_rating:data.student_rating_no, 17 | author:data.author.name, 18 | price:data.price, 19 | student_no:data.student_no 20 | } 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ) 32 | } 33 | 34 | export const getServerSideProps=async({req,res,query:{course_uuid}})=>{ 35 | const resAPI=await fetch(`${BACKEND_URI}/courses/detail/${course_uuid}/`) 36 | 37 | 38 | if (!resAPI.ok){ 39 | return { 40 | redirect:{ 41 | destination:"/", 42 | permanent:false 43 | } 44 | } 45 | } 46 | 47 | const data=await resAPI.json() 48 | 49 | 50 | return { 51 | props:{data} 52 | } 53 | 54 | } 55 | 56 | export default course_uuid 57 | -------------------------------------------------------------------------------- /pages/course/sector/[sector_uuid].js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainLayout from '../../../components/layouts/main' 3 | import CourseListCard from '../../../components/courses/list/CourseListCard' 4 | import { BACKEND_URI } from '../../../config/app' 5 | 6 | const sector_uuid = ({data:{sector_name,data:courses,total_students}}) => { 7 | 8 | 9 | return ( 10 | 11 |
12 |

13 | {sector_name[0].toUpperCase()+sector_name.slice(1,sector_name.length)} Courses 14 |

15 | 16 |

17 | 18 | 19 | {total_students} learners 20 |

21 | 22 |
23 | 24 |
25 |

26 | All {sector_name[0].toUpperCase()+sector_name.slice(1,sector_name.length)} Courses 27 |

28 |

29 | {sector_name[0].toUpperCase()+sector_name.slice(1,sector_name.length)} instructors on Udemy specialize in everything from software development to data analysis, and are known for their effective, friendly instruction for students of all levels. 30 | 31 |

32 | 33 |
34 |
35 | 36 |
37 |

38 | Not sure? All courses have a 30-day money-back guarantee 39 | 40 |

41 |
42 |
43 | 44 |
45 | { 46 | courses.map(course=> 47 | 48 | () 49 | ) 50 | } 51 | {/* 52 | */} 53 | 54 |
55 |
56 | ) 57 | } 58 | 59 | export const getServerSideProps=async({req,res,query:{sector_uuid}})=>{ 60 | const resAPI=await fetch(`${BACKEND_URI}/courses/${sector_uuid}/`) 61 | 62 | 63 | if (!resAPI.ok){ 64 | return { 65 | redirect:{ 66 | destination:'/', 67 | permanent:false 68 | } 69 | } 70 | } 71 | 72 | const data=await resAPI.json() 73 | 74 | // console.log(data) 75 | 76 | 77 | return { 78 | props:{data} 79 | } 80 | 81 | } 82 | 83 | export default sector_uuid 84 | -------------------------------------------------------------------------------- /pages/course/study/[course_uuid].js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import WatchLayout from "../../../components/layouts/watch" 3 | import WatchArea from '../../../components/courses/study/WatchArea' 4 | import CommentArea from '../../../components/courses/study/CommentArea' 5 | import cookie from 'cookie' 6 | import refreshToken from "../../api/refresh_token" 7 | import { BACKEND_URI } from '../../../config/app' 8 | 9 | 10 | const course_uuid = ({data}) => { 11 | const [comments,setComments]=useState(data.comment) 12 | const [title,setTitle]=useState(data.title) 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export const getServerSideProps=async({req,res,query:{course_uuid}})=>{ 23 | 24 | 25 | if (!req.headers.cookie){ 26 | 27 | 28 | return { 29 | redirect:{ 30 | destination:'/auth/login', 31 | permanent:false 32 | } 33 | } 34 | 35 | } 36 | 37 | let {refresh_token}=cookie.parse(req.headers.cookie) 38 | 39 | if (!refresh_token){ 40 | 41 | 42 | return { 43 | redirect:{ 44 | destination:'/auth/login', 45 | permanent:false 46 | } 47 | } 48 | } 49 | 50 | 51 | 52 | let {access_token}=cookie.parse(req.headers.cookie) 53 | 54 | 55 | if (!access_token){ 56 | const refreshRes= await refreshToken(req,res) 57 | 58 | 59 | if(refreshRes){ 60 | access_token=refreshRes 61 | }else{ 62 | 63 | 64 | return { 65 | redirect:{ 66 | destination:'/auth/login', 67 | permanent:false 68 | } 69 | } 70 | } 71 | 72 | 73 | 74 | } 75 | 76 | let resAPI = await fetch(`${BACKEND_URI}/courses/study/${course_uuid}/`,{ 77 | method:"GET", 78 | headers:{ 79 | "Content-type":"application/json", 80 | "Authorization": `Token ${access_token}` 81 | } 82 | 83 | }) 84 | 85 | 86 | if (resAPI.ok){ 87 | const data= await resAPI.json() 88 | 89 | // console.log(data) 90 | 91 | // send data in response 92 | 93 | return { 94 | props:{data} 95 | } 96 | 97 | }else{ 98 | // send error message 99 | 100 | 101 | return { 102 | redirect:{ 103 | destination:'/', 104 | permanent:false 105 | } 106 | } 107 | } 108 | 109 | 110 | 111 | } 112 | 113 | export default course_uuid 114 | -------------------------------------------------------------------------------- /pages/index.jsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Image from 'next/image' 3 | import MainLayout from '../components/layouts/main' 4 | import Header from '../components/index/Header' 5 | import CourseSuggest from '../components/index/CourseSuggest' 6 | import CourseList from '../components/index/CourseList' 7 | import CategoryList from '../components/index/CategoryList' 8 | import TeachUdemy from '../components/index/TeachUdemy' 9 | import TrustedCompanies from '../components/index/TrustedCompanies' 10 | import { BACKEND_URI } from '../config/app' 11 | 12 | 13 | 14 | 15 | export default function Home({data}) { 16 | 17 | 18 | return ( 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ) 34 | } 35 | 36 | export const getServerSideProps=async({req,res})=>{ 37 | const resAPI=await fetch(`${BACKEND_URI}/courses/`) 38 | 39 | if (!resAPI.ok){ 40 | return { 41 | props:{} 42 | } 43 | } 44 | 45 | const data=await resAPI.json() 46 | 47 | 48 | 49 | return { 50 | props:{data} 51 | } 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /pages/search/[term].jsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Image from 'next/image' 3 | import { useRouter } from 'next/router' 4 | import MainLayout from '../../components/layouts/main' 5 | import CourseListCard from '../../components/courses/list/CourseListCard' 6 | import { BACKEND_URI } from '../../config/app' 7 | 8 | 9 | export default function Search({data}) { 10 | const router=useRouter() 11 | const {query:{term}}=router 12 | 13 | 14 | 15 | return ( 16 | 17 | 18 |
19 |

20 | Courses on terms "{ 21 | term 22 | }" 23 |

24 |
25 | 26 | {data && data.length ? 27 |
28 | { 29 | data.map(course=> 30 | 31 | () 32 | ) 33 | } 34 | 35 | 36 |
: 37 | 38 |
39 |

40 | No Course found on the provided term "{term}" 41 |

42 |
43 | } 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | ) 55 | } 56 | 57 | export const getServerSideProps=async({req,res,query:{term}})=>{ 58 | const resAPI=await fetch(`${BACKEND_URI}/courses/search/${term}/`) 59 | 60 | 61 | // if (!resAPI.ok){ 62 | // return { 63 | // redirect:{ 64 | // destination:'/', 65 | // permanent:false 66 | // } 67 | // } 68 | // } 69 | 70 | const data=await resAPI.json() 71 | 72 | // console.log(data) 73 | 74 | 75 | return { 76 | props:{data} 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /pages/user.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState,useEffect, useContext } from 'react' 2 | import MainLayout from '../components/layouts/main' 3 | import CourseItem from '../components/user/courseItem' 4 | import AuthContext from '../context/AuthContext' 5 | import { BACKEND_URI } from '../config/app' 6 | import cookie from 'cookie' 7 | import refreshToken from './api/refresh_token' 8 | 9 | function user() { 10 | 11 | const [courseDetail,setCourseDetail]=useState([]) 12 | const [courseReady,setCourseReady]=useState(false) 13 | 14 | const {user:{courses}}=useContext(AuthContext) 15 | 16 | useEffect(async()=>{ 17 | if(courses.length >0){ 18 | 19 | const res = await fetch(`${BACKEND_URI}/courses/cart/`,{ 20 | method:'POST', 21 | headers:{ 22 | 'Content-type':'application/json' 23 | }, 24 | body:JSON.stringify({cart:courses}), 25 | 26 | 27 | }) 28 | 29 | 30 | 31 | if(res.ok){ 32 | 33 | const details = await res.json() 34 | 35 | setCourseDetail([...details.cart_detail]) 36 | 37 | setCourseReady(true) 38 | 39 | } 40 | 41 | 42 | } 43 | 44 | },[]) 45 | 46 | return ( 47 | 48 |
49 |

50 | Your Courses 51 |

52 | 53 |
54 | 55 |
56 |
57 |

58 | {courses.length} Course 59 |

60 | 61 | {courseDetail.length > 0 && courseReady ? ( 62 | <> 63 | { 64 | courseDetail.length ? courses.map((item,index)=>{ 65 | 66 | 67 | const obj=courseDetail.find(detail=>item==detail.course_uuid) 68 | 69 | 70 | return () 71 | }):'' 72 | } 73 | 74 | 75 | ) : 76 | 77 |

You have no courses yet

78 | } 79 | 80 | 81 |
82 | 83 | 84 | 85 |
86 |
87 | ) 88 | } 89 | 90 | export const getServerSideProps=async({req,res,query:{course_uuid}})=>{ 91 | 92 | 93 | if (!req.headers.cookie){ 94 | 95 | 96 | return { 97 | redirect:{ 98 | destination:'/auth/login', 99 | permanent:false 100 | } 101 | } 102 | 103 | } 104 | 105 | let {refresh_token}=cookie.parse(req.headers.cookie) 106 | 107 | if (!refresh_token){ 108 | 109 | 110 | return { 111 | redirect:{ 112 | destination:'/auth/login', 113 | permanent:false 114 | } 115 | } 116 | } 117 | 118 | 119 | 120 | let {access_token}=cookie.parse(req.headers.cookie) 121 | 122 | 123 | if (!access_token){ 124 | const refreshRes= await refreshToken(req,res) 125 | 126 | 127 | if(refreshRes){ 128 | access_token=refreshRes 129 | }else{ 130 | 131 | 132 | return { 133 | redirect:{ 134 | destination:'/auth/login', 135 | permanent:false 136 | } 137 | } 138 | } 139 | 140 | 141 | 142 | } 143 | 144 | 145 | 146 | 147 | return { 148 | props:{} 149 | } 150 | 151 | 152 | 153 | 154 | } 155 | 156 | 157 | export default user 158 | -------------------------------------------------------------------------------- /public/assets/pexels-louis.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OtchereDev/Udemy_Nextjs/8dd386999b444b0f7a175b68163146406a9c4e47/public/assets/pexels-louis.jpg -------------------------------------------------------------------------------- /public/assets/udemy-b1.svg: -------------------------------------------------------------------------------- 1 | booking -------------------------------------------------------------------------------- /public/assets/udemy-b2.svg: -------------------------------------------------------------------------------- 1 | volkswagen -------------------------------------------------------------------------------- /public/assets/udemy-b3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 52 | 53 | -------------------------------------------------------------------------------- /public/assets/udemy-b4.svg: -------------------------------------------------------------------------------- 1 | adidas -------------------------------------------------------------------------------- /public/assets/udemy-b5.svg: -------------------------------------------------------------------------------- 1 | eventbrite -------------------------------------------------------------------------------- /public/assets/udemy-red-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Logo 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/assets/udemy1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OtchereDev/Udemy_Nextjs/8dd386999b444b0f7a175b68163146406a9c4e47/public/assets/udemy1.jpg -------------------------------------------------------------------------------- /public/assets/udemy_logo-red.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/assets/udemy_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/assets/udemy_teacher.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OtchereDev/Udemy_Nextjs/8dd386999b444b0f7a175b68163146406a9c4e47/public/assets/udemy_teacher.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OtchereDev/Udemy_Nextjs/8dd386999b444b0f7a175b68163146406a9c4e47/public/favicon.ico -------------------------------------------------------------------------------- /redux/slices/AuthSlices.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | const initialState = { 4 | user:null 5 | } 6 | 7 | export const authSlice = createSlice({ 8 | name: 'auth', 9 | initialState, 10 | reducers: { 11 | addUser: (state,action) => { 12 | // console.log(action.payload) 13 | // if(!action.payload===undefined){ 14 | // console.log('i happen') 15 | // } 16 | state.user = action.payload 17 | }, 18 | removeUser: (state) => { 19 | state.user = null 20 | } 21 | }, 22 | }) 23 | 24 | // Action creators are generated for each case reducer function 25 | export const { addUser,removeUser } = authSlice.actions 26 | 27 | export default authSlice.reducer -------------------------------------------------------------------------------- /redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit' 2 | import AuthReducer from "./slices/AuthSlices" 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | auth:AuthReducer 7 | }, 8 | }) --------------------------------------------------------------------------------