├── .env.example ├── .gitignore ├── .gitpod.yml ├── README.md ├── app ├── account │ ├── page.js │ ├── page.module.scss │ └── search-history │ │ ├── page.js │ │ └── page.module.scss ├── globals.scss ├── layout.js ├── mixins.scss ├── not-found.js ├── not-found.module.scss ├── page.js ├── page.module.scss ├── search │ ├── (all) │ │ ├── page.js │ │ └── page.module.scss │ ├── books │ │ └── page.js │ ├── images │ │ ├── page.js │ │ └── page.module.scss │ ├── layout.js │ ├── layout.module.scss │ ├── loading.js │ ├── news │ │ └── page.js │ └── videos │ │ ├── page.js │ │ └── page.module.scss └── variables.scss ├── components ├── pages │ ├── all │ │ ├── Card │ │ │ ├── card.module.scss │ │ │ └── index.js │ │ ├── CardSkeleton │ │ │ └── index.js │ │ ├── Snippet │ │ │ ├── index.js │ │ │ └── style.module.scss │ │ └── SnippetSkeleton │ │ │ └── index.js │ ├── images │ │ ├── Card │ │ │ ├── index.js │ │ │ └── style.module.scss │ │ └── CardSkeleton │ │ │ └── index.js │ ├── news │ │ └── NewsCard │ │ │ └── index.js │ └── videos │ │ ├── CardSkeleton │ │ └── index.js │ │ └── VideoCard │ │ ├── index.js │ │ └── videoCard.module.scss └── shared │ ├── AccountBtn │ ├── index.js │ └── style.module.scss │ ├── Footer │ ├── footer.module.scss │ └── index.js │ ├── GhStarBtn │ ├── ghStarBtn.module.scss │ └── index.js │ ├── Loader │ ├── index.js │ └── loader.module.scss │ ├── LoadmoreBtn │ ├── index.js │ └── loadmorebtn.module.scss │ ├── NoResults │ ├── index.js │ └── noResults.module.scss │ ├── SearchBar │ ├── index.js │ └── style.module.scss │ └── ThemeBtn │ ├── index.js │ └── themeBtn.module.scss ├── context ├── AuthContext.js └── ThemeContext.js ├── hooks └── useLocalStorage.js ├── jsconfig.json ├── next.config.js ├── package.json ├── pages └── api │ ├── auth │ └── [...nextauth].js │ ├── card.js │ ├── history.js │ └── search │ ├── images.js │ ├── index.js │ ├── news.js │ └── videos.js ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── favicon.png ├── images │ ├── home.png │ ├── home_old.png │ ├── logo.svg │ ├── page_1.png │ ├── page_2.png │ ├── page_3.png │ └── screenshot.png ├── logo.png ├── site.webmanifest └── vercel.svg └── utils ├── db.js ├── fetchImageResults.js ├── fetchNewsResults.js ├── fetchSearchResults.js ├── fetchVideosResults.js └── getInitialColorMode.js /.env.example: -------------------------------------------------------------------------------- 1 | GOOGLE_API_KEY= 2 | GOOGLE_API_CX= 3 | 4 | YOUTUBE_API_KEY= 5 | 6 | NEWS_API_KEY= 7 | OPENAI_API_KEY= 8 | 9 | NEXTAUTH_URL= 10 | NEXTAUTH_SECRET= 11 | 12 | GITHUB_ID= 13 | GITHUB_SECRET= 14 | 15 | AUTH0_ISSUER_BASE_URL 16 | AUTH0_CLIENT_ID 17 | AUTH0_CLIENT_SECRET= 18 | 19 | MONGODB_USERNAME= 20 | MONGODB_PASSWORD= 21 | MONGODB_HOST= 22 | MONGODB_DB= -------------------------------------------------------------------------------- /.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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env.development 31 | 32 | # vercel 33 | .vercel 34 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | # Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart 6 | 7 | tasks: 8 | - init: pnpm install && pnpm run build 9 | command: pnpm run start 10 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | SearchEx Logo 3 | 4 |
5 | 6 | ## About The Project 7 | SearchEx is a search engine clone developed using NextJs, providing a powerful and intuitive search experience. It allows users to search for web pages, images, news, and videos. 8 | 9 | ### Features 10 | * Features 11 | * Clean and user-friendly UI 12 | * Comprehensive search capabilities 13 | * Intelligent auto-suggestions 14 | * Search history page 15 | * Profile management 16 | * User authentication with GitHub and auth0 17 | * Pagination system for search results 18 | * OpenAI integration for enhanced search intelligence 19 | * Light & dark theme options 20 | * Fully responsive design 21 | 22 | ## Getting Started 23 | 24 | To get started with this project, you can simply clone this repository and install the necessary dependencies. 25 | 26 | ```bash 27 | git clone https://github.com/devxprite/searchex.git 28 | cd searchex 29 | npm install 30 | ``` 31 | 32 | ### Configuration 33 | Before running the project, make sure to set up the environment variables in a .env file located in the root directory of the project. Below is a sample .env file: 34 | ``` 35 | GOOGLE_API_KEY= 36 | GOOGLE_API_CX= 37 | 38 | YOUTUBE_API_KEY= 39 | 40 | NEWS_API_KEY= 41 | OPENAI_API_KEY= 42 | 43 | NEXTAUTH_URL= 44 | NEXTAUTH_SECRET= 45 | 46 | GITHUB_ID= 47 | GITHUB_SECRET= 48 | 49 | AUTH0_ISSUER_BASE_URL 50 | AUTH0_CLIENT_ID 51 | AUTH0_CLIENT_SECRET= 52 | 53 | MONGODB_USERNAME= 54 | MONGODB_PASSWORD= 55 | MONGODB_HOST= 56 | MONGODB_DB= 57 | ``` 58 | 59 | ### Running the Project 60 | 61 | Once you have set up the environment variables, you can start the development server with the following command: 62 | ```bash 63 | npm run dev 64 | ``` 65 | This will start the Next.js development server at http://localhost:3000. 66 | The website auto-updates as you edit the file. 67 | 68 | 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. 69 | 70 | ## Screenshots 71 | ![Screenshot 1](./public/images/home.png) 72 | ![Screenshot 2](./public/images/page_1.png) 73 | ![Screenshot 3](./public/images/page_2.png) 74 | ![Screenshot 4](./public/images/page_3.png) 75 | 76 | 77 | ## License 78 | This project is licensed under the MIT License. -------------------------------------------------------------------------------- /app/account/page.js: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useSession, signIn, signOut } from 'next-auth/react'; 4 | import styles from "./page.module.scss"; 5 | import Link from "next/link"; 6 | import { useRouter } from "next/navigation"; 7 | 8 | import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' 9 | import 'react-loading-skeleton/dist/skeleton.css' 10 | 11 | export default function Page() { 12 | const { data: session, status } = useSession(); 13 | const router = useRouter(); 14 | 15 | 16 | if (status === 'loading') { 17 | return ( 18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 | ) 34 | } 35 | 36 | if (!session) { 37 | signIn() 38 | } 39 | 40 | const UserEmail = session?.user?.email; 41 | const UserName = session.user.name || session.user.email.split('@')[0]; 42 | const UserImage = session.user?.image; 43 | 44 | return ( 45 |
46 |
47 |
48 | User Image 49 |
50 |
51 |

{UserName}

52 |

{UserEmail}

53 |
54 | 55 | 56 |
57 |
58 |
59 |
60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /app/account/page.module.scss: -------------------------------------------------------------------------------- 1 | @import "@/app/mixins"; 2 | 3 | .account_page { 4 | min-height: 100vh; 5 | display: grid; 6 | place-content: center; 7 | 8 | .card { 9 | margin: auto; 10 | // display: inline-block; 11 | padding: 2rem 1.5rem; 12 | background: var(--bg-1); 13 | border-radius: 0.5rem; 14 | font-weight: normal; 15 | transition: all 0.3s; 16 | @include gradient-border(var(--gradient-2), 0.5rem, 2px); 17 | 18 | @include mobile { 19 | max-width: 94%; 20 | } 21 | 22 | 23 | .userImg { 24 | max-width: 14rem; 25 | border-radius: 50%; 26 | margin-bottom: 2.5rem; 27 | box-shadow: var(--box-shadow); 28 | border: 2px solid var(--border-color); 29 | transition: all 0.3s; 30 | 31 | @include mobile { 32 | margin-bottom: 1rem; 33 | max-width: 70%; 34 | } 35 | 36 | &:hover { 37 | box-shadow: var(--box-shadow-hover); 38 | transform: scale(1.05); 39 | } 40 | } 41 | 42 | .name { 43 | @include gradient-text(var(--gradient-2)); 44 | font-weight: normal; 45 | font-size: 2.2rem; 46 | } 47 | 48 | .email { 49 | font-size: 1.5rem; 50 | color: var(--color-2); 51 | } 52 | 53 | .buttons { 54 | display: flex; 55 | justify-content: space-around; 56 | gap: 2.5rem; 57 | margin-top: 3rem; 58 | 59 | @include mobile { 60 | flex-direction: column; 61 | gap: 1rem; 62 | } 63 | 64 | button, 65 | .btn { 66 | // color: var(--color); 67 | color: black; 68 | font-size: 1.1rem; 69 | padding: 0.5rem 1rem; 70 | border-radius: 0.3rem; 71 | border: 1px solid var(--border-color); 72 | // background: var(--bg); 73 | background: var(--gradient-2); 74 | box-shadow: var(--box-shadow); 75 | transition: all 0.3s; 76 | font-family: 'Ubuntu', sans-serif; 77 | cursor: pointer; 78 | 79 | &:hover { 80 | box-shadow: var(--box-shadow-hover); 81 | // background: var(--bg-1); 82 | transform: scale(1.05); 83 | } 84 | 85 | @include mobile { 86 | width: 100%; 87 | } 88 | 89 | &.logout { 90 | background: orangered; 91 | } 92 | } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /app/account/search-history/page.js: -------------------------------------------------------------------------------- 1 | import { getServerSession } from "next-auth" 2 | import mongoClient from "@/utils/db" 3 | import styles from "./page.module.scss"; 4 | import { authOptions } from '@/pages/api/auth/[...nextauth]' 5 | 6 | export default async function Page() { 7 | 8 | const session = await getServerSession(authOptions); 9 | const email = session?.user?.email; 10 | const name = session?.user?.name || session?.user?.email.split('@')[0]; 11 | 12 | const client = await mongoClient; 13 | const db = client.db(); 14 | 15 | console.log(email); 16 | 17 | const searchHistory = await db.collection("history").find({ 18 | email: email 19 | }).limit(200).toArray(); 20 | 21 | 22 | 23 | console.log(searchHistory); 24 | 25 | return ( 26 |
27 |

Search History of {name}

28 | 29 | {(searchHistory.length > 0) 30 | ? ( 31 |
32 | {searchHistory.map((item, index) => ( 33 |

34 | 35 | {(item.localTimestamp)} 36 | 37 | 38 | {item.query} 39 | 40 | 41 | {item.path} 42 | 43 |

44 | ))} 45 |
46 | ) 47 | : (

No Search History found!

) 48 | } 49 | 50 |
51 | ) 52 | } -------------------------------------------------------------------------------- /app/account/search-history/page.module.scss: -------------------------------------------------------------------------------- 1 | @import '@/app/mixins'; 2 | 3 | .page > h2{ 4 | margin: 10vh 0 5vh 0; 5 | font-size: 2.5rem; 6 | } 7 | 8 | .history{ 9 | 10 | max-width: 40rem; 11 | background-color: var(--bg-2); 12 | margin: auto; 13 | padding: 0.5rem; 14 | border-radius: 0.5rem; 15 | 16 | 17 | .item{ 18 | 19 | display: grid; 20 | grid-template-columns: 1fr 2fr 1fr; 21 | 22 | &__query{ 23 | font-size: 1.2rem; 24 | color: var(--color); 25 | } 26 | 27 | &__path{ 28 | padding-left: 0.5rem; 29 | font-size: 1rem; 30 | color: var(--color-2); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/globals.scss: -------------------------------------------------------------------------------- 1 | @import '@/app/mixins'; 2 | @import '@/app/variables'; 3 | 4 | *, 5 | *::after, 6 | *::before { 7 | box-sizing: border-box; 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | html{ 13 | ::-webkit-scrollbar { 14 | width: 0.4rem; 15 | 16 | @include mobile { 17 | width: 0.2rem; 18 | } 19 | } 20 | 21 | ::-webkit-scrollbar-track { 22 | background: var(--bg) 23 | } 24 | 25 | ::-webkit-scrollbar-thumb { 26 | background: var(--color-3); 27 | border-radius: 0.5rem; 28 | } 29 | 30 | ::-webkit-scrollbar-thumb:hover { 31 | background: var(--color-2); 32 | } 33 | } 34 | 35 | body { 36 | background-color: var(--bg); 37 | color: var(--color); 38 | text-align: center; 39 | font-family: 'Ubuntu', sans-serif; 40 | min-height: 100vh; 41 | -webkit-tap-highlight-color: transparent; 42 | 43 | main{ 44 | // flex: 1; 45 | min-height: 100vh; 46 | } 47 | } 48 | 49 | a{ 50 | color: var(--link-color); 51 | text-decoration: none; 52 | } -------------------------------------------------------------------------------- /app/layout.js: -------------------------------------------------------------------------------- 1 | import { headers } from 'next/headers' 2 | import AuthContext from '@/context/AuthContext'; 3 | import Footer from '@/components/shared/Footer' 4 | import './globals.scss' 5 | import { ThemeProvider } from '@/context/ThemeContext'; 6 | 7 | export const metadata = { 8 | title: 'SearchEx', 9 | description: 'Effortlessly explore the web', 10 | keywords: ['Next.js', 'React', 'JavaScript'], 11 | authors: [{ name: 'DevXprite', url: 'https://github.com/devxprite' }], 12 | colorScheme: 'dark', 13 | favicon: '/favicon.png', 14 | alternates: { 15 | canonical: '/' 16 | }, 17 | openGraph: { 18 | images: '/images/screenshot.png', 19 | type: 'website', 20 | }, 21 | }; 22 | 23 | async function getSession(cookies) { 24 | const response = await fetch(`${process.env.NEXTAUTH_URL}/api/auth/session`, { 25 | headers: { 26 | cookies, 27 | }, 28 | }); 29 | 30 | const session = await response.json(); 31 | 32 | return Object.keys(session).length > 0 ? session : null; 33 | } 34 | 35 | export default async function RootLayout({ children }) { 36 | 37 | const session = await getSession(headers().get('cookie') ?? ''); 38 | 39 | return ( 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | {children} 52 | 53 |
54 |