├── .babelrc ├── .gitignore ├── CONTRIBUTE.md ├── LICENSE ├── README.md ├── components.json ├── gulpfile.mjs ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── api │ ├── auth │ │ └── [...nextauth].ts │ ├── github │ │ └── add-project.ts │ └── init-analytics.ts ├── blog │ ├── [slug].tsx │ └── index.tsx ├── contribute │ └── index.tsx ├── index.tsx ├── learn.tsx └── learn │ ├── game-development │ ├── index.tsx │ └── project │ │ └── [id].tsx │ ├── machine-learning-and-ai │ ├── index.tsx │ └── project │ │ └── [id].tsx │ ├── mobile-development │ ├── index.tsx │ └── project │ │ └── [id].tsx │ └── web-development │ ├── index.tsx │ └── project │ └── [id].tsx ├── postcss.config.js ├── posts └── projectlearn-project-based-learning.md ├── public ├── data.json ├── fonts │ ├── Lato-Black.ttf │ ├── Lato-BlackItalic.ttf │ ├── Lato-Bold.ttf │ ├── Lato-BoldItalic.ttf │ ├── Lato-Italic.ttf │ ├── Lato-Light.ttf │ ├── Lato-LightItalic.ttf │ ├── Lato-Regular.ttf │ ├── Lato-Thin.ttf │ └── Lato-ThinItalic.ttf ├── img │ ├── C.jpg │ ├── C.png │ ├── Csharp.jpg │ ├── Csharp.png │ ├── art.svg │ ├── design.jpg │ ├── design.png │ ├── java.jpg │ ├── java.png │ ├── javascript.jpg │ ├── javascript.png │ ├── logo.png │ ├── nodejs.jpg │ ├── nodejs.png │ ├── placeholder.png │ ├── python.jpg │ ├── python.png │ ├── react.jpg │ └── react.png ├── projectlearn.png ├── robots.txt └── static │ └── images │ ├── posts │ └── what_is_projectlearn.svg │ └── profiles │ └── Xtremilicious.jpg ├── src ├── components │ ├── GitHubLogin.tsx │ ├── blog │ │ ├── BlogList.tsx │ │ ├── BlogSplash.tsx │ │ └── Footer.tsx │ ├── dashboard │ │ ├── Content │ │ │ ├── Project.tsx │ │ │ ├── ProjectList.tsx │ │ │ └── index.tsx │ │ ├── Info │ │ │ ├── ProjectInfo.tsx │ │ │ ├── RelatedProject.tsx │ │ │ └── RelatedProjects.tsx │ │ ├── Layout.tsx │ │ └── Sidebar │ │ │ ├── Categories.tsx │ │ │ ├── CategoryInfo.tsx │ │ │ └── index.tsx │ ├── landing │ │ ├── Categories.tsx │ │ ├── Features.tsx │ │ ├── Footer.tsx │ │ ├── Navbar.tsx │ │ ├── Newsletter.tsx │ │ └── Splash.tsx │ ├── theme-provider.tsx │ └── ui │ │ ├── accordion.tsx │ │ ├── alert.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── command.tsx │ │ ├── date-picker.tsx │ │ ├── dialog.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── loading-spinner.tsx │ │ ├── multi-select.tsx │ │ ├── popover.tsx │ │ └── select.tsx ├── images │ ├── challenge.png │ ├── coding.png │ ├── game-dev-cat.png │ ├── game-dev-cat.webp │ ├── logo.png │ ├── ml-cat-alt.png │ ├── ml-cat-alt.webp │ ├── ml-cat.png │ ├── mob-dev-cat.png │ ├── pl-splash.png │ ├── pl-splash.svg │ ├── pl-splash.webp │ ├── powered-by-namecheap-black.png │ ├── powered-by-namecheap.png │ ├── prog-lang-cat.png │ ├── prog-lang-cat.webp │ ├── rocket.png │ ├── web-dev-cat-alt.png │ ├── web-dev-cat-alt.webp │ └── web-dev-cat.png ├── lib │ ├── analytics.js │ └── utils.ts ├── redux │ ├── actions │ │ └── dataActions.ts │ ├── reducers │ │ └── dataReducer.ts │ ├── store.ts │ └── types.ts └── utils │ ├── data.ts │ ├── functions.ts │ ├── generate_readme.ts │ ├── generate_sitemap.ts │ ├── styles.ts │ └── technologies.ts ├── styles ├── contribute.module.scss ├── globals.css └── markdown-styles.module.scss ├── tailwind.config.js └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel" 4 | ], 5 | "plugins": [ 6 | [ 7 | "styled-components", 8 | { 9 | "ssr": true, 10 | "displayName": true, 11 | "preprocess": false 12 | } 13 | ] 14 | ] 15 | } -------------------------------------------------------------------------------- /.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 | .env* 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | .now -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # Contributing to ProjectLearn 2 | 3 | As this project is community-driven, feel free to open an issue (or even better, send a Pull Request) for expanding this list. Contributions are very much welcome. 4 | 5 | 6 | ## Project List Contribution 7 | 8 | Note: Cloning the repository is not required. You can directly use the GitHub Online File Editor. 9 | 10 | 1. Ensure the project tutorial to be added doesn't already exist and it is of good quality (code quality, project scope & project relevance). 11 | 2. Update the [data.json](https://github.com/Xtremilicious/ProjectLearn-Project-Based-Learning/blob/master/public/data.json) file. Project links must be pointing straight to the tutorials, no URL shorteners. 12 | 13 | ## Feature Contribution 14 | 15 | To get started, just clone the repository and run `npm install && npm run dev`: 16 | 17 | git clone https://github.com/Xtremilicious/projectlearn-project-based-learning.git 18 | npm ci 19 | npm run dev 20 | 21 | Thank you for your contributions! If you think there is anything to improve with the guidelines or any kind of constructive criticism, please create an issue [here](https://github.com/Xtremilicious/projectlearn-project-based-learning/issues/new) with the details. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nilarjun Das 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "styles/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /gulpfile.mjs: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import imagemin, { optipng } from 'gulp-imagemin'; 3 | import imageminWebp from 'imagemin-webp'; 4 | 5 | // Task to optimize PNG images 6 | gulp.task('optimize-png', () => { 7 | return gulp.src('src/images/*.png') 8 | .pipe(imagemin([ 9 | optipng({ optimizationLevel: 5 }), 10 | ])) 11 | .pipe(gulp.dest('src/images')); 12 | }); 13 | 14 | // Task to optimize WebP images 15 | gulp.task('optimize-webp', () => { 16 | return gulp.src('src/images/*.webp') 17 | .pipe(imagemin([ 18 | imageminWebp({ quality: 100 }), 19 | ])) 20 | .pipe(gulp.dest('src/images')); 21 | }); 22 | 23 | // Default task to run both image optimization tasks 24 | gulp.task('default', gulp.series('optimize-png', 'optimize-webp')); 25 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // NOTE: This file should not be edited 4 | // see https://nextjs.org/docs/basic-features/typescript for more information. 5 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withImages = require("next-images"); 2 | const projectsData = require("./public/data.json"); 3 | const { PHASE_DEVELOPMENT_SERVER } = require("next/constants"); 4 | const glob = require("glob"); 5 | 6 | module.exports = (phase, { defaultConfig }) => 7 | withImages({ 8 | webpack: function (config) { 9 | config.module.rules.push({ 10 | test: /\.md$/, 11 | use: "raw-loader", 12 | }); 13 | return config; 14 | }, 15 | env: { 16 | EMAIL_API: process.env.EMAIL_API 17 | }, 18 | images: { 19 | disableStaticImages: true 20 | }, 21 | typescript:{ 22 | ignoreBuildErrors: true 23 | }, 24 | exportPathMap: async function (defaultPathMap, { dev, dir, outDir, distDir, buildId }) { 25 | const paths = { 26 | "/": { page: "/", query: { __nextDefaultLocale: '' } }, 27 | "/blog": { page: "/blog" }, 28 | "/learn/web-development": { 29 | page: "/learn/web-development", 30 | }, 31 | "/learn/game-development": { 32 | page: "/learn/game-development", 33 | }, 34 | "/learn/mobile-development": { 35 | page: "/learn/mobile-development", 36 | }, 37 | "/learn/machine-learning-and-ai": { 38 | page: "/learn/machine-learning-and-ai", 39 | }, 40 | }; 41 | 42 | if (phase !== PHASE_DEVELOPMENT_SERVER) { 43 | projectsData.map((project) => { 44 | project.category.map((t) => { 45 | const slug = 46 | t === "web-dev" 47 | ? "web-development" 48 | : t === "mob-dev" 49 | ? "mobile-development" 50 | : t === "game-dev" 51 | ? "game-development" 52 | : "machine-learning-and-ai"; 53 | 54 | let urlTitle = project.title.toLowerCase().split(" ").join("-"); 55 | let imageUrl = project.imageUrl; // Add this line to get the image URL from project data 56 | 57 | paths[`/learn/${slug}/project/${urlTitle}-${project.id}`] = { 58 | page: `/learn/${slug}/project/[id]`, 59 | query: { id: `${urlTitle}-${project.id}`, imageUrl }, // Pass imageUrl as query parameter 60 | }; 61 | }); 62 | }); 63 | } 64 | 65 | const blogs = glob.sync("posts/**/*.md"); 66 | 67 | // Filter out any invalid file paths or directories 68 | const validBlogs = blogs.filter((file) => { 69 | return file.includes("/") && file.endsWith(".md"); 70 | }); 71 | 72 | // Extract blog slugs from valid file paths 73 | const blogSlugs = validBlogs.map((file) => { 74 | const parts = file.split("/"); 75 | const fileName = parts[parts.length - 1]; 76 | return fileName.slice(0, -3); // Remove the file extension (.md) 77 | }); 78 | 79 | // Add each blog to the paths object 80 | blogSlugs.forEach((blog) => { 81 | paths[`/blog/${blog}`] = { page: "/blog/[slug]", query: { slug: blog } }; 82 | }); 83 | 84 | return paths; 85 | }, 86 | }); 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "projectlearn", 3 | "version": "3.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "export": "next build && next export && node ./src/utils/generate_sitemap.ts", 10 | "generate-readme": "node ./src/utils/generate_readme.ts && git add .", 11 | "optimize-images": "gulp" 12 | }, 13 | "pre-commit": [ 14 | "generate-readme" 15 | ], 16 | "dependencies": { 17 | "@amplitude/analytics-browser": "^2.11.7", 18 | "@fortawesome/fontawesome-svg-core": "^1.2.27", 19 | "@fortawesome/free-brands-svg-icons": "^5.15.4", 20 | "@fortawesome/free-solid-svg-icons": "^5.15.4", 21 | "@fortawesome/react-fontawesome": "^0.1.14", 22 | "@hookform/resolvers": "^3.9.0", 23 | "@radix-ui/react-accordion": "^1.2.1", 24 | "@radix-ui/react-avatar": "^1.1.1", 25 | "@radix-ui/react-dialog": "^1.1.2", 26 | "@radix-ui/react-label": "^2.1.0", 27 | "@radix-ui/react-popover": "^1.1.2", 28 | "@radix-ui/react-select": "^2.1.2", 29 | "@radix-ui/react-slot": "^1.1.0", 30 | "@reduxjs/toolkit": "^2.2.1", 31 | "axios": "^0.24.0", 32 | "bootstrap": "^5.3.0", 33 | "class-variance-authority": "^0.7.0", 34 | "clsx": "^2.1.1", 35 | "cmdk": "^1.0.0", 36 | "date-fns": "^3.6.0", 37 | "glob": "^10.3.10", 38 | "gray-matter": "^4.0.2", 39 | "lucide-react": "^0.451.0", 40 | "next": "^12.0.0", 41 | "next-auth": "^4.24.8", 42 | "next-images": "^1.8.5", 43 | "next-redux-wrapper": "^7.0.0", 44 | "next-themes": "^0.3.0", 45 | "nextjs-sitemap-generator": "^1.3.1", 46 | "node-glob": "^1.2.0", 47 | "raw-loader": "^4.0.2", 48 | "react": "^18.2.0", 49 | "react-day-picker": "^8.10.1", 50 | "react-dom": "^18.2.0", 51 | "react-hook-form": "^7.53.0", 52 | "react-icons": "^4.3.1", 53 | "react-markdown": "^6.0.0", 54 | "react-redux": "^7.2.5", 55 | "react-youtube": "^8.0.0", 56 | "redux": "^4.1.0", 57 | "redux-devtools-extension": "^2.13.9", 58 | "redux-thunk": "^2.4.0", 59 | "sass": "^1.79.5", 60 | "styled-components": "^5.3.0", 61 | "tailwind-merge": "^2.5.3", 62 | "tailwindcss-animate": "^1.0.7", 63 | "zod": "^3.23.8" 64 | }, 65 | "devDependencies": { 66 | "@types/node": "^22.7.5", 67 | "@types/react": "^18.3.11", 68 | "@types/styled-components": "^5.1.34", 69 | "autoprefixer": "^10.4.20", 70 | "babel-plugin-styled-components": "^1.10.7", 71 | "gulp": "^5.0.0", 72 | "gulp-imagemin": "^9.1.0", 73 | "imagemin-webp": "^8.0.0", 74 | "postcss": "^8.4.47", 75 | "pre-commit": "^1.2.2", 76 | "tailwindcss": "^3.4.13", 77 | "typescript": "^5.6.3" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import Head from "next/head"; 3 | import Router from "next/router"; 4 | import Script from "next/script"; 5 | import { Provider } from "react-redux"; 6 | 7 | // Custom imports 8 | import { analytics } from '../src/lib/analytics'; 9 | import store from "../src/redux/store"; 10 | import { ThemeProvider } from "@/components/theme-provider"; 11 | 12 | // Types 13 | import type { AppProps } from "next/app"; 14 | 15 | import globalStyles from "../src/utils/styles"; 16 | import "../styles/globals.css" 17 | 18 | function trackPageView(url: string) { 19 | try { 20 | window.gtag('config', 'UA-141654226-3', { 21 | page_location: url, 22 | }); 23 | } catch (error) { 24 | console.error("Error tracking page view:", error); 25 | } 26 | } 27 | 28 | const AppWrapper = ({ Component, pageProps }: AppProps) => { 29 | useEffect(() => { 30 | // Initialize Amplitude 31 | analytics.init(); 32 | 33 | // Track page views on route change 34 | const handleRouteChange = (url: string) => { 35 | trackPageView(url); 36 | }; 37 | 38 | Router.events.on('routeChangeComplete', handleRouteChange); 39 | 40 | // Cleanup the event listener on unmount 41 | return () => { 42 | Router.events.off('routeChangeComplete', handleRouteChange); 43 | }; 44 | }, []); 45 | 46 | return ( 47 |
48 | 49 | ProjectLearn 50 | 51 | 52 | 67 | 68 | {/* Google Tag Manager */} 69 |