├── .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 |
56 |
67 |
70 |
71 |
76 |
77 |
78 |
79 |
80 | );
81 | };
82 |
83 | export default AppWrapper;
84 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from "next/document";
2 | import { ServerStyleSheet } from "styled-components";
3 |
4 | export default class MyDocument extends Document {
5 | static async getInitialProps(ctx) {
6 | const sheet = new ServerStyleSheet();
7 | const originalRenderPage = ctx.renderPage;
8 |
9 | try {
10 | ctx.renderPage = () =>
11 | originalRenderPage({
12 | enhanceApp: (App) => (props) =>
13 | sheet.collectStyles( ),
14 | });
15 |
16 | const initialProps = await Document.getInitialProps(ctx);
17 | return {
18 | ...initialProps,
19 | styles: (
20 | <>
21 | {initialProps.styles}
22 | {sheet.getStyleElement()}
23 | >
24 | ),
25 | };
26 | } finally {
27 | sheet.seal();
28 | }
29 | }
30 |
31 | render() {
32 | return (
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
50 |
56 |
57 |
61 |
62 |
67 |
68 | {/* Google Tag Manager */}
69 |
78 | {/* End Google Tag Manager */}
79 |
80 |
81 |
82 |
83 | {/* Google Tag Manager (noscript) */}
84 |
85 |
91 |
92 | {/* End Google Tag Manager (noscript) */}
93 |
94 |
95 | );
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/pages/api/auth/[...nextauth].ts:
--------------------------------------------------------------------------------
1 | import NextAuth from 'next-auth';
2 | import GitHubProvider from 'next-auth/providers/github';
3 |
4 | export default NextAuth({
5 | providers: [
6 | GitHubProvider({
7 | clientId: process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID,
8 | clientSecret: process.env.GITHUB_CLIENT_SECRET,
9 | authorization: { params: { scope: 'public_repo' } },
10 | }),
11 | ],
12 | callbacks: {
13 | async jwt({ token, account }) {
14 | if (account && account.access_token) {
15 | token.accessToken = account.access_token;
16 | }
17 | return token;
18 | },
19 | async session({ session, token }) {
20 | session.accessToken = token.accessToken;
21 | return session;
22 | },
23 | },
24 | secret: process.env.NEXTAUTH_SECRET,
25 | });
26 |
--------------------------------------------------------------------------------
/pages/api/github/add-project.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { getToken } from "next-auth/jwt";
3 |
4 | const slug = (name: string) =>
5 | name
6 | .split(" ")
7 | .map((part) => part.toLowerCase())
8 | .join("-");
9 |
10 | export default async function handler(req, res) {
11 | const token = await getToken({ req }); // Get the session from the request
12 |
13 | // Ensure the session exists and access token is available
14 | if (!token || !token.accessToken) {
15 | console.error("Missing access token");
16 | return res
17 | .status(401)
18 | .json({ error: "Unauthorized: Access token is required" });
19 | }
20 |
21 | const { projects } = req.body;
22 | const { accessToken } = token;
23 |
24 | // Utility function to introduce delay
25 | const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
26 |
27 | // Retry function to fetch repository details with retries if the repo is not yet ready
28 | const retryFetch = async (fn, retries = 5, delayTime = 2000) => {
29 | for (let i = 0; i < retries; i++) {
30 | try {
31 | return await fn();
32 | } catch (error) {
33 | if (i < retries - 1) {
34 | console.log(`Retrying after ${delayTime}ms... (${i + 1}/${retries})`);
35 | await delay(delayTime);
36 | } else {
37 | throw error;
38 | }
39 | }
40 | }
41 | };
42 |
43 | try {
44 | const baseBranch = "master";
45 | const forkedRepoOwner = token.name; // Assuming the fork is created under the user's GitHub account
46 |
47 | // Step 1: Check if the fork already exists
48 | let forkExists = false;
49 | try {
50 | const forkCheckResponse = await axios.get(
51 | `https://api.github.com/repos/${forkedRepoOwner}/${process.env.GITHUB_REPO_NAME}`,
52 | {
53 | headers: {
54 | Authorization: `token ${accessToken}`,
55 | },
56 | }
57 | );
58 | forkExists = true;
59 | } catch (error) {
60 | if (error.response && error.response.status === 404) {
61 | console.log("Fork does not exist, creating a new fork...");
62 | } else {
63 | throw error; // If the error is not 404, rethrow it
64 | }
65 | }
66 |
67 | // Step 2: Fork the repository if it doesn't exist
68 | if (!forkExists) {
69 | const forkResponse = await axios.post(
70 | `https://api.github.com/repos/${process.env.GITHUB_REPO_OWNER}/${process.env.GITHUB_REPO_NAME}/forks`,
71 | {},
72 | {
73 | headers: {
74 | Authorization: `token ${accessToken}`,
75 | },
76 | }
77 | );
78 |
79 | // Wait for some time before proceeding to allow GitHub to process the fork
80 | console.log("Waiting for the fork to be ready...");
81 | await delay(5000); // Wait for 5 seconds (increase if necessary)
82 | }
83 |
84 | // Step 3: Sync the fork with the latest changes from the original repository (if fork exists)
85 | if (forkExists) {
86 | const branchResponse = await retryFetch(() =>
87 | axios.get(
88 | `https://api.github.com/repos/${process.env.GITHUB_REPO_OWNER}/${process.env.GITHUB_REPO_NAME}/git/refs/heads/${baseBranch}`,
89 | {
90 | headers: {
91 | Authorization: `token ${accessToken}`,
92 | },
93 | }
94 | )
95 | );
96 |
97 | const latestSha = branchResponse.data.object.sha;
98 |
99 | // Update the fork by fast-forwarding it to the latest commit from the main repo
100 | await axios.patch(
101 | `https://api.github.com/repos/${forkedRepoOwner}/${process.env.GITHUB_REPO_NAME}/git/refs/heads/${baseBranch}`,
102 | {
103 | sha: latestSha,
104 | force: true, // Force update to ensure the fork syncs correctly
105 | },
106 | {
107 | headers: {
108 | Authorization: `token ${accessToken}`,
109 | },
110 | }
111 | );
112 | console.log("Fork synced with the latest commit");
113 | }
114 |
115 | // Step 4: Fetch current data.json from the forked repo
116 | const contentResponse = await retryFetch(() =>
117 | axios.get(
118 | `https://api.github.com/repos/${forkedRepoOwner}/${process.env.GITHUB_REPO_NAME}/contents/public/data.json`,
119 | {
120 | headers: {
121 | Authorization: `token ${accessToken}`,
122 | Accept: "application/vnd.github.v3+json",
123 | },
124 | }
125 | )
126 | );
127 |
128 | const sha = contentResponse.data.sha;
129 | const content = Buffer.from(
130 | contentResponse.data.content,
131 | "base64"
132 | ).toString("utf-8");
133 | const existingProjectsData = JSON.parse(content);
134 |
135 | // Step 5: Find the highest existing id
136 | const highestId = existingProjectsData.reduce(
137 | (max, project) => Math.max(max, project.id || 0),
138 | 0
139 | );
140 |
141 | // Step 6: Append new project data with unique id
142 | const updatedProjectsData = [
143 | ...projects.map((project, index) => ({
144 | ...project,
145 | id: highestId + index + 1, // Assign new id based on the highest existing id
146 | })),
147 | ...existingProjectsData,
148 | ];
149 |
150 | // Step 7: Create new branch in the fork
151 | const newBranch = `${slug(token.name)}/add-project-${Date.now()}`;
152 |
153 | // Fetch base branch SHA from the forked repo
154 | const branchResponse = await axios.get(
155 | `https://api.github.com/repos/${forkedRepoOwner}/${process.env.GITHUB_REPO_NAME}/git/refs/heads/master`,
156 | {
157 | headers: {
158 | Authorization: `token ${accessToken}`,
159 | },
160 | }
161 | );
162 |
163 | await axios.post(
164 | `https://api.github.com/repos/${forkedRepoOwner}/${process.env.GITHUB_REPO_NAME}/git/refs`,
165 | {
166 | ref: `refs/heads/${newBranch}`,
167 | sha: branchResponse.data.object.sha,
168 | },
169 | {
170 | headers: {
171 | Authorization: `token ${accessToken}`,
172 | },
173 | }
174 | );
175 |
176 | // Step 8: Update data.json in the forked repo
177 | const updatedContent = Buffer.from(
178 | JSON.stringify(updatedProjectsData, null, 2)
179 | ).toString("base64");
180 |
181 | await axios.put(
182 | `https://api.github.com/repos/${forkedRepoOwner}/${process.env.GITHUB_REPO_NAME}/contents/public/data.json`,
183 | {
184 | message: "Add new projects",
185 | content: updatedContent,
186 | branch: newBranch,
187 | sha,
188 | },
189 | {
190 | headers: {
191 | Authorization: `token ${accessToken}`,
192 | },
193 | }
194 | );
195 |
196 | // Step 9: Create pull request from fork to main repo
197 | const pullRequestResponse = await axios.post(
198 | `https://api.github.com/repos/${process.env.GITHUB_REPO_OWNER}/${process.env.GITHUB_REPO_NAME}/pulls`,
199 | {
200 | title: `${token.name}: Project submission`,
201 | head: `${forkedRepoOwner}:${newBranch}`, // head branch comes from user's fork
202 | base: baseBranch,
203 | },
204 | {
205 | headers: {
206 | Authorization: `token ${accessToken}`,
207 | },
208 | }
209 | );
210 |
211 | res.status(200).json({
212 | message: "Pull request created",
213 | pullRequestUrl: pullRequestResponse.data.html_url,
214 | });
215 | } catch (error) {
216 | console.error(
217 | "Error creating pull request:",
218 | error.response ? error.response.data : error.message
219 | );
220 | res.status(500).json({ error: "Failed to add projects" });
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/pages/api/init-analytics.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import { init } from '@amplitude/analytics-browser';
3 |
4 | export default function handler(req: NextApiRequest, res: NextApiResponse) {
5 | if (req.method === 'POST') {
6 | try {
7 | const apiKey = process.env.AMPLITUDE_API_KEY;
8 | if (!apiKey) {
9 | return res.status(500).json({ error: 'Amplitude API key not defined' });
10 | }
11 |
12 | //init(apiKey); // Initialize Amplitude
13 | res.status(200).json({ message: 'Amplitude initialized successfully' });
14 |
15 | // Log initialization in development mode
16 | if (process.env.NODE_ENV === 'development') {
17 | console.log('Amplitude initialized');
18 | }
19 | } catch (error) {
20 | console.error('Error initializing Amplitude:', error);
21 | res.status(500).json({ error: 'Failed to initialize Amplitude' });
22 | }
23 | } else {
24 | // Handle any other HTTP method
25 | res.setHeader('Allow', ['POST']);
26 | res.status(405).end(`Method ${req.method} Not Allowed`);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pages/blog/[slug].tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import matter from "gray-matter";
3 | import ReactMarkdown from "react-markdown";
4 | import styled from "styled-components";
5 | import Head from "next/head";
6 | import Link from "next/link";
7 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
8 | import { faArrowLeft } from "@fortawesome/free-solid-svg-icons";
9 |
10 | import Footer from "@/components/blog/Footer";
11 |
12 | const ArticleWrapper = styled.div`
13 | margin-bottom: 1vh;
14 | img {
15 | max-width: 100%;
16 | }
17 | h2 {
18 | border-bottom: 1px solid #f2f2f2;
19 | padding-bottom: 0.5rem;
20 | width: 100%;
21 | }
22 | .back {
23 | position: absolute;
24 | background: #2e2e2e;
25 | border-radius: 50%;
26 | padding: 2vh;
27 | top: 2vw;
28 | width: fit-content;
29 | border-radius: 2vh;
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 | left: 2vw;
34 | color: white;
35 | font-size: 2vw;
36 | cursor: pointer;
37 | transition: 0.5s;
38 | .back-text {
39 | margin-left: 1vh;
40 | }
41 | :hover {
42 | box-shadow: 0 1px 7px rgba(0, 0, 0, 0.05);
43 | }
44 | }
45 | .author,
46 | .date {
47 | font-weight: normal;
48 | font-size: 1.5vw;
49 | margin: 0;
50 | }
51 | .date {
52 | font-weight: 200;
53 | }
54 | .blog h1 {
55 | margin: 0.7rem;
56 | font-size: 3.5vw;
57 | }
58 | .blog__hero {
59 | height: 60vh;
60 | margin: 0;
61 | overflow: hidden;
62 | }
63 | .blog__hero img {
64 | height: 100%;
65 | width: 100%;
66 | object-fit: cover;
67 | }
68 | .blog__info {
69 | padding: 1.5rem 0;
70 | display: flex;
71 | flex-direction: column;
72 | margin: 0 10%;
73 | }
74 | .blog__info h1 {
75 | margin-bottom: 0.66rem;
76 | }
77 | .blog__info h3 {
78 | margin-bottom: 0;
79 | }
80 | .blog__body {
81 | display: flex;
82 | width: 80%;
83 | margin: 0 auto;
84 | flex-direction: column;
85 | align-items: flex-start;
86 | font-size: 1.35vw;
87 | padding-bottom: 2vw;
88 | }
89 | .blog__body a {
90 | padding-bottom: 1.5rem;
91 | color: var(--button-blue);
92 | }
93 | .blog__body:last-child {
94 | margin-bottom: 0;
95 | }
96 | .blog__body h1 h2 h3 h4 h5 h6 p {
97 | font-weight: normal;
98 | }
99 | .blog__body p {
100 | color: inherit;
101 | }
102 | .blog__body ul {
103 | list-style: initial;
104 | }
105 | .blog__body ul ol {
106 | margin-left: 1.25rem;
107 | margin-bottom: 1.25rem;
108 | padding-left: 1.45rem;
109 | }
110 | .article-meta {
111 | display: flex;
112 | align-items: center;
113 | margin-left: 3vh;
114 | }
115 | .author-image {
116 | display: flex;
117 |
118 | align-items: center;
119 | margin-right: 1vh;
120 | }
121 | .author-image img {
122 | height: 3.7vw;
123 | width: 3.7vw;
124 | border-radius: 50%;
125 | overflow: hidden;
126 | object-position: center;
127 | object-fit: cover;
128 | }
129 | .meta-info {
130 | display: flex;
131 | justify-content: center;
132 | flex-direction: column;
133 | }
134 | @media only screen and (min-width: 320px) and (max-width: 480px) {
135 | margin-bottom: 3vh;
136 | .blog__hero {
137 | height: 30vh;
138 | }
139 | .blog__info {
140 | margin: 0 5%;
141 | }
142 | .blog__info h1 {
143 | font-size: 7vw;
144 | text-align: center;
145 | }
146 | .blog__info h3 {
147 | margin-bottom: 0;
148 | font-size: 4vw;
149 | }
150 | .blog__body {
151 | font-size: 4vw;
152 | }
153 | .back {
154 | font-size: 3.5vw;
155 | padding: 1.5vh;
156 | }
157 | .author-image img {
158 | height: 9vw;
159 | width: 9vw;
160 | }
161 | .article-meta {
162 | margin-top: 2vh;
163 | justify-content: center;
164 | margin-left: 0vh;
165 | }
166 | }
167 | `;
168 |
169 | export default function BlogTemplate(props) {
170 | function reformatDate(fullDate) {
171 | const date = new Date(fullDate);
172 | const day = date.getDate();
173 | const month = date.toLocaleString("default", { month: "short" });
174 | const year = date.getFullYear();
175 | return `${day} ${month}, ${year}`;
176 | }
177 | const { post } = props;
178 |
179 | useEffect(() => {
180 | var links = document.getElementsByTagName("a");
181 | var len = links.length;
182 |
183 | for (var i = 0; i < len; i++) {
184 | links[i].target = "_blank";
185 | }
186 | });
187 |
188 | return (
189 |
190 |
191 |
192 | {`${post.frontmatter.title} | ProjectLearn`}
193 | {/* */}
194 |
195 |
196 |
197 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
{post.frontmatter.title}
208 |
209 |
210 |
211 |
212 |
213 |
{post.frontmatter.author}
214 | {reformatDate(post.frontmatter.date)}
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | );
226 | }
227 |
228 | BlogTemplate.getInitialProps = async function (ctx) {
229 | const { slug } = ctx.query;
230 | const content = await import(`../../posts/${slug}.md`);
231 | const data = matter(content.default);
232 |
233 | return {
234 | post: {
235 | fileRelativePath: `src/posts/${slug}.md`,
236 | frontmatter: data.data,
237 | markdownBody: data.content,
238 | },
239 | };
240 | };
241 |
--------------------------------------------------------------------------------
/pages/blog/index.tsx:
--------------------------------------------------------------------------------
1 | import matter from "gray-matter";
2 |
3 | import BlogList from "@/components/blog/BlogList";
4 | import Navbar from "@/components/landing/Navbar";
5 | import Newsletter from "@/components/landing/Newsletter";
6 | import Footer from "@/components/landing/Footer";
7 | import Head from "next/head";
8 |
9 | import styled from "styled-components";
10 |
11 | const BlogWrapper = styled.div`
12 | min-height: 100vh;
13 | background: #fafafa;
14 | padding-bottom: 5vh;
15 | @media only screen and (min-width: 320px) and (max-width: 480px) {
16 | padding-bottom:0vh;
17 | }
18 | `;
19 |
20 | const Index = (props) => {
21 | return (
22 |
23 |
24 |
25 |
29 |
30 |
35 |
36 |
ProjectLearn Blog - All Things Tech
37 | {/* */}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | );
47 | };
48 |
49 | export default Index;
50 |
51 | Index.getInitialProps = async function () {
52 | // get all blog data for list
53 | const posts = ((context) => {
54 | const keys = context.keys();
55 | const values = keys.map(context);
56 | const data = keys.map((key, index) => {
57 | // Create slug from filename
58 | const slug = key
59 | .replace(/^.*[\\\/]/, "")
60 | .split(".")
61 | .slice(0, -1)
62 | .join(".");
63 | const value = values[index];
64 | // Parse yaml metadata & markdownbody in document
65 | const document = matter(value.default);
66 | return {
67 | document,
68 | slug,
69 | };
70 | });
71 | return data;
72 | })(require.context("../../posts", true, /\.md$/));
73 |
74 | return {
75 | allBlogs: posts,
76 | };
77 | };
78 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import Head from "next/head";
3 | import Navbar from "@/components/landing/Navbar";
4 | import Splash from "@/components/landing/Splash";
5 | import Categories from "@/components/landing/Categories";
6 | import Newsletter from "@/components/landing/Newsletter";
7 | import Footer from "@/components/landing/Footer";
8 | import styled from "styled-components";
9 |
10 |
11 | const IndexWrapper = styled.div`
12 | background-color: var(--themeDark);
13 | `
14 |
15 |
16 |
17 | export default class Landing extends Component {
18 | render() {
19 | return (
20 |
21 |
22 |
23 |
27 |
31 | {/* */}
32 | ProjectLearn - Learn to Code by Creating Projects
33 |
34 |
35 |
36 |
37 |
38 | {/* */}
39 |
40 |
41 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pages/learn.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | export default class learn extends Component {
4 | render() {
5 | return (
6 |
7 | This is projects showcase
8 |
9 | )
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/pages/learn/game-development/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | import { useDispatch } from "react-redux";
4 |
5 | // Components
6 | import Head from "next/head";
7 | import Layout from "@/components/dashboard/Layout";
8 |
9 | // Actions
10 | import { getProjects } from "../../../src/redux/actions/dataActions";
11 |
12 | const gameDevelopment = () => {
13 | const dispatch = useDispatch();
14 |
15 | useEffect(() => {
16 | dispatch(getProjects());
17 | }, [dispatch]);
18 | return (
19 |
20 |
21 |
25 |
29 |
33 | {/* */}
34 |
Learn Game Development | ProjectLearn
35 |
36 |
42 |
43 | );
44 | };
45 |
46 | export default gameDevelopment;
47 |
--------------------------------------------------------------------------------
/pages/learn/game-development/project/[id].tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useRouter } from "next/router";
3 | import { useDispatch, connect } from "react-redux";
4 |
5 | // Components
6 | import Head from "next/head";
7 | import ProjectInfo from "@/components/dashboard/Info/ProjectInfo";
8 |
9 | // Actions
10 | import { getProjects } from "../../../../src/redux/actions/dataActions";
11 |
12 | const Project = (props) => {
13 | const { projects } = props;
14 |
15 | const router = useRouter();
16 |
17 | const dispatch = useDispatch();
18 |
19 | useEffect(() => {
20 | dispatch(getProjects());
21 | }, [dispatch]);
22 |
23 | let projectTitle, projectID;
24 |
25 | const array = router.query?.id?.split("-");
26 |
27 | if (!array) {
28 | return;
29 | }
30 |
31 | projectTitle = array.slice(0, array.length - 1).map((item) => item);
32 | projectID = array[array.length - 1];
33 |
34 | let project;
35 |
36 | if (!projects) {
37 | return null;
38 | }
39 |
40 | project = projects.filter(
41 | (project) =>
42 | project.title.toLowerCase().split(/\s/).join("").split("-").join("") ==
43 | projectTitle.join("") && project.id == parseInt(projectID)
44 | )[0];
45 |
46 | const projectCategory = "Game Development";
47 | const slug = "game-development";
48 |
49 | let article;
50 | let flag = 0;
51 | if (
52 | project.title.toLowerCase()[0] === "a" ||
53 | project.title.toLowerCase()[0] === "e" ||
54 | project.title.toLowerCase()[0] === "i" ||
55 | project.title.toLowerCase()[0] === "o" ||
56 | project.title.toLowerCase()[0] === "u"
57 | ) {
58 | flag = 1;
59 | }
60 |
61 | article = flag === 1 ? "an" : "a";
62 |
63 | return project != null ? (
64 |
65 |
66 |
67 |
75 |
76 |
82 |
83 |
{`Build ${article} ${project.title} - ${projectCategory} Project | ProjectLearn`}
84 | {/* */}
85 |
86 |
91 |
92 | ) : (
93 | "Project Not Found"
94 | );
95 | };
96 |
97 | const mapStateToProps = (state) => {
98 | return {
99 | projects: state.data.projects,
100 | };
101 | };
102 |
103 | export default connect(mapStateToProps, {})(Project);
104 |
--------------------------------------------------------------------------------
/pages/learn/machine-learning-and-ai/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useDispatch } from "react-redux";
3 |
4 | // Components
5 | import Head from "next/head";
6 | import Layout from "@/components/dashboard/Layout";
7 |
8 | // Actions
9 | import { getProjects } from "../../../src/redux/actions/dataActions";
10 |
11 | const machineLearningAI = () => {
12 | const dispatch = useDispatch();
13 |
14 | useEffect(() => {
15 | dispatch(getProjects());
16 | }, [dispatch]);
17 | return (
18 |
19 |
20 |
24 |
28 |
32 | {/* */}
33 |
Learn Machine Learning and AI | ProjectLearn
34 |
35 |
41 |
42 | );
43 | };
44 |
45 | export default machineLearningAI;
46 |
--------------------------------------------------------------------------------
/pages/learn/machine-learning-and-ai/project/[id].tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useRouter } from "next/router";
3 | import { connect, useDispatch } from "react-redux";
4 |
5 | // Components
6 | import Head from "next/head";
7 | import ProjectInfo from "@/components/dashboard/Info/ProjectInfo";
8 |
9 | // Actions
10 | import { getProjects } from "../../../../src/redux/actions/dataActions";
11 |
12 | const Project = (props) => {
13 | const { projects } = props;
14 |
15 | const router = useRouter();
16 |
17 | const dispatch = useDispatch();
18 |
19 | useEffect(() => {
20 | dispatch(getProjects());
21 | }, [dispatch]);
22 |
23 | let projectTitle, projectID;
24 |
25 | const array = router.query?.id?.split("-");
26 |
27 | if (!array) {
28 | return;
29 | }
30 |
31 | projectTitle = array.slice(0, array.length - 1).map((item) => item);
32 | projectID = array[array.length - 1];
33 |
34 | let project;
35 |
36 | if (!projects) {
37 | return null;
38 | }
39 |
40 | project = projects.filter(
41 | (project) =>
42 | project.title.toLowerCase().split(/\s/).join("").split("-").join("") ==
43 | projectTitle.join("") && project.id == parseInt(projectID)
44 | )[0];
45 |
46 | const projectCategory = "Machine Learning and AI";
47 | const slug = "machine-learning-and-ai";
48 |
49 | let article;
50 | let flag = 0;
51 | if (
52 | project.title.toLowerCase()[0] === "a" ||
53 | project.title.toLowerCase()[0] === "e" ||
54 | project.title.toLowerCase()[0] === "i" ||
55 | project.title.toLowerCase()[0] === "o" ||
56 | project.title.toLowerCase()[0] === "u"
57 | ) {
58 | flag = 1;
59 | }
60 |
61 | article = flag === 1 ? "an" : "a";
62 |
63 | return project != null ? (
64 |
65 |
66 |
67 |
75 |
76 |
82 |
83 |
{`Build ${article} ${project.title} - ${projectCategory} Project | ProjectLearn`}
84 | {/* */}
85 |
86 |
91 |
92 | ) : (
93 | "Project Not Found"
94 | );
95 | };
96 |
97 | const mapStateToProps = (state) => {
98 | return {
99 | projects: state.data.projects,
100 | };
101 | };
102 |
103 | export default connect(mapStateToProps, {})(Project);
104 |
--------------------------------------------------------------------------------
/pages/learn/mobile-development/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useDispatch } from "react-redux";
3 |
4 | // Components
5 | import Head from "next/head";
6 | import Layout from "@/components/dashboard/Layout";
7 |
8 | // Actions
9 | import { getProjects } from "../../../src/redux/actions/dataActions";
10 |
11 | const mobileDevelopment = () => {
12 | const dispatch = useDispatch();
13 |
14 | useEffect(() => {
15 | dispatch(getProjects());
16 | }, [dispatch]);
17 | return (
18 |
19 |
20 |
24 |
28 |
32 | {/* */}
33 |
Learn Mobile Development | ProjectLearn
34 |
35 |
41 |
42 | );
43 | };
44 |
45 | export default mobileDevelopment;
46 |
--------------------------------------------------------------------------------
/pages/learn/mobile-development/project/[id].tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useRouter } from "next/router";
3 | import { connect, useDispatch } from "react-redux";
4 |
5 | // Components
6 | import Head from "next/head";
7 | import ProjectInfo from "@/components/dashboard/Info/ProjectInfo";
8 |
9 | // Actions
10 | import { getProjects } from "../../../../src/redux/actions/dataActions";
11 |
12 | const Project = (props) => {
13 | const { projects } = props;
14 |
15 | const router = useRouter();
16 |
17 | const dispatch = useDispatch();
18 |
19 | useEffect(() => {
20 | dispatch(getProjects());
21 | }, [dispatch]);
22 |
23 | let projectTitle, projectID;
24 |
25 | const array = router.query?.id?.split("-");
26 |
27 | if (!array) {
28 | return;
29 | }
30 |
31 | projectTitle = array.slice(0, array.length - 1).map((item) => item);
32 | projectID = array[array.length - 1];
33 |
34 | let project;
35 |
36 | if (!projects) {
37 | return null;
38 | }
39 |
40 | project = projects.filter(
41 | (project) =>
42 | project.title.toLowerCase().split(/\s/).join("").split("-").join("") ==
43 | projectTitle.join("") && project.id == parseInt(projectID)
44 | )[0];
45 |
46 | const projectCategory = "Mobile Development";
47 | const slug = "mobile-development";
48 |
49 | let article;
50 | let flag = 0;
51 | if (
52 | project.title.toLowerCase()[0] === "a" ||
53 | project.title.toLowerCase()[0] === "e" ||
54 | project.title.toLowerCase()[0] === "i" ||
55 | project.title.toLowerCase()[0] === "o" ||
56 | project.title.toLowerCase()[0] === "u"
57 | ) {
58 | flag = 1;
59 | }
60 |
61 | article = flag === 1 ? "an" : "a";
62 |
63 | return project != null ? (
64 |
65 |
66 |
67 |
74 |
75 |
81 |
82 |
{`Build ${article} ${project.title} - ${projectCategory} Project | ProjectLearn`}
83 | {/* */}
84 |
85 |
90 |
91 | ) : (
92 | "Project Not Found"
93 | );
94 | };
95 |
96 | const mapStateToProps = (state) => {
97 | return {
98 | projects: state.data.projects,
99 | };
100 | };
101 |
102 | export default connect(mapStateToProps, {})(Project);
103 |
--------------------------------------------------------------------------------
/pages/learn/web-development/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import Head from "next/head";
3 | import { useDispatch } from "react-redux";
4 |
5 | // Components
6 | import Layout from "@/components/dashboard/Layout";
7 |
8 | // Actions
9 | import { getProjects } from "../../../src/redux/actions/dataActions";
10 |
11 |
12 | const WebDevelopment = () => {
13 | const dispatch = useDispatch();
14 |
15 | useEffect(() => {
16 | dispatch(getProjects());
17 | }, [dispatch]);
18 |
19 | return (
20 |
21 |
22 |
26 |
30 |
34 | {/* */}
35 |
Learn Web Development | ProjectLearn
36 |
37 |
43 |
44 | );
45 | };
46 |
47 | export default WebDevelopment;
48 |
--------------------------------------------------------------------------------
/pages/learn/web-development/project/[id].tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useRouter } from "next/router";
3 |
4 | import { connect, useDispatch } from "react-redux";
5 |
6 | // Components
7 | import Head from "next/head";
8 | import ProjectInfo from "@/components/dashboard/Info/ProjectInfo";
9 |
10 | // Actions
11 | import { getProjects } from "../../../../src/redux/actions/dataActions";
12 |
13 | const Project = (props) => {
14 | const { projects } = props;
15 |
16 | const router = useRouter();
17 |
18 | const dispatch = useDispatch();
19 |
20 | useEffect(() => {
21 | dispatch(getProjects());
22 | }, [dispatch]);
23 |
24 | let projectTitle, projectID;
25 |
26 | const array = router.query?.id?.split("-");
27 |
28 | if (!array) {
29 | return;
30 | }
31 |
32 | projectTitle = array.slice(0, array.length - 1).map((item) => item);
33 | projectID = array[array.length - 1] ?? "";
34 |
35 | let project;
36 |
37 | if (!projects) {
38 | return null;
39 | }
40 |
41 | project = projects.filter(
42 | (project) =>
43 | project.title.toLowerCase().split(/\s/).join("").split("-").join("") ==
44 | projectTitle.join("") && project.id == parseInt(projectID)
45 | )[0];
46 |
47 | const projectCategory = "Web Development";
48 | const slug = "web-development";
49 |
50 | let article;
51 | let flag = 0;
52 | if (
53 | project.title.toLowerCase()[0] === "a" ||
54 | project.title.toLowerCase()[0] === "e" ||
55 | project.title.toLowerCase()[0] === "i" ||
56 | project.title.toLowerCase()[0] === "o" ||
57 | project.title.toLowerCase()[0] === "u"
58 | ) {
59 | flag = 1;
60 | }
61 |
62 | article = flag === 1 ? "an" : "a";
63 |
64 | return project != null ? (
65 |
66 |
67 |
68 |
75 |
76 |
82 |
83 |
{`Build ${article} ${project.title} - ${projectCategory} Project | ProjectLearn`}
84 | {/* */}
85 |
86 |
91 |
92 | ) : (
93 | "Project Not Found"
94 | );
95 | };
96 |
97 | const mapStateToProps = (state) => {
98 | return {
99 | projects: state.data.projects,
100 | };
101 | };
102 |
103 | export default connect(mapStateToProps, {})(Project);
104 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/posts/projectlearn-project-based-learning.md:
--------------------------------------------------------------------------------
1 | ---
2 | author: Nilarjun Das
3 | author_image: /static/images/profiles/Xtremilicious.jpg
4 | date: '2020-04-05T07:15:32.623Z'
5 | hero_image: /static/images/posts/what_is_projectlearn.svg
6 | title: 'What is ProjectLearn - Project-Based Learning?'
7 | ---
8 |
9 | ## The Project-Based Learning Approach
10 | Project Based Learning is a learning approach in which students gain concepts, knowledge and skills by working on a real project to investigate and respond to an authentic, engaging, and complex question, problem, or challenge.
11 |
12 | ## What is ProjectLearn?
13 | [ProjectLearn](https://projectlearn.io) provides a curated list of project tutorials in which learners build an application from scratch. These are divided into different categories, namely, web development, mobile development, game development, machine learning, deep learning and artificial intelligence.
14 |
15 | The list has project tutorials on many in-demand languages and technologies including:
16 |
17 |
18 | - ReactJS, NodeJS, VueJS, Angular, Django for [Web Development](https://projectlearn.io/learn/web-development)
19 | - Flutter, React Native, Swift, Java, Kotlin for [Mobile App Development](https://projectlearn.io/learn/mobile-development)
20 | - C#, .NET Core, Unity, JavaScript for [Game Development](https://projectlearn.io/learn/game-development)
21 | - Python, TensorFlow, Numpy, Pandas, OpenCV, Keras, PyTorch for [Machine Learning and Artifical Intelligence](https://projectlearn.io/learn/machine-learning-and-ai)
22 |
23 | This project is community-driven. Feel free to open an issue (or even better, send a Pull Request) for expanding this list of awesome project tutorials. Contributions are very much welcome.
24 |
25 | GitHub: https://github.com/Xtremilicious/ProjectLearn-Project-Based-Learning
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/public/fonts/Lato-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/fonts/Lato-Black.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-BlackItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/fonts/Lato-BlackItalic.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/fonts/Lato-Bold.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/fonts/Lato-BoldItalic.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/fonts/Lato-Italic.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/fonts/Lato-Light.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/fonts/Lato-LightItalic.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/fonts/Lato-Regular.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/fonts/Lato-Thin.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-ThinItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/fonts/Lato-ThinItalic.ttf
--------------------------------------------------------------------------------
/public/img/C.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/C.jpg
--------------------------------------------------------------------------------
/public/img/C.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/C.png
--------------------------------------------------------------------------------
/public/img/Csharp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/Csharp.jpg
--------------------------------------------------------------------------------
/public/img/Csharp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/Csharp.png
--------------------------------------------------------------------------------
/public/img/design.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/design.jpg
--------------------------------------------------------------------------------
/public/img/design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/design.png
--------------------------------------------------------------------------------
/public/img/java.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/java.jpg
--------------------------------------------------------------------------------
/public/img/java.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/java.png
--------------------------------------------------------------------------------
/public/img/javascript.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/javascript.jpg
--------------------------------------------------------------------------------
/public/img/javascript.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/javascript.png
--------------------------------------------------------------------------------
/public/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/logo.png
--------------------------------------------------------------------------------
/public/img/nodejs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/nodejs.jpg
--------------------------------------------------------------------------------
/public/img/nodejs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/nodejs.png
--------------------------------------------------------------------------------
/public/img/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/placeholder.png
--------------------------------------------------------------------------------
/public/img/python.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/python.jpg
--------------------------------------------------------------------------------
/public/img/python.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/python.png
--------------------------------------------------------------------------------
/public/img/react.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/react.jpg
--------------------------------------------------------------------------------
/public/img/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/img/react.png
--------------------------------------------------------------------------------
/public/projectlearn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/projectlearn.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | Sitemap: https://projectlearn.io/sitemap.xml
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/public/static/images/profiles/Xtremilicious.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/public/static/images/profiles/Xtremilicious.jpg
--------------------------------------------------------------------------------
/src/components/GitHubLogin.tsx:
--------------------------------------------------------------------------------
1 | // components/GithubLoginButton.js
2 | import { useEffect, useState } from "react";
3 | import { getSession } from "next-auth/react";
4 | import { signIn, signOut } from "next-auth/react";
5 |
6 | import { Info } from "lucide-react";
7 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
8 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
9 | import { analytics } from "@/lib/analytics";
10 |
11 | const GithubLoginButton = () => {
12 | const [session, setSession] = useState(null);
13 |
14 | useEffect(() => {
15 | const fetchSession = async () => {
16 | const sessionData = await getSession();
17 | setSession(sessionData);
18 | };
19 |
20 | fetchSession();
21 | }, []);
22 |
23 | const handleLogin = () => {
24 | // Redirect to the NextAuth authentication route
25 | //window.location.href = "/api/auth/signin/github";
26 | analytics.track("Sign In Clicked", {});
27 | setTimeout(() => {
28 | signIn("github");
29 | }, 1500);
30 | };
31 |
32 | const handleLogout = () => {
33 | //window.location.href = "/api/auth/signout";
34 | analytics.track("Sign Out Clicked", {
35 | user: session?.user,
36 | });
37 | setTimeout(() => {
38 | signOut();
39 | }, 1500);
40 | };
41 |
42 | return (
43 |
44 | {session ? (
45 |
46 |
47 |
48 | Welcome, {session.user.name}
49 |
50 |
51 |
52 | CN
53 |
54 |
55 |
60 | Logout
61 |
62 |
63 | ) : (
64 | <>
65 |
66 |
67 |
68 | Please{" "}
69 |
70 | log in
71 | {" "}
72 | using your GitHub account to add projects directly.
73 |
74 |
75 | >
76 | )}
77 |
78 | );
79 | };
80 |
81 | export default GithubLoginButton;
82 |
--------------------------------------------------------------------------------
/src/components/blog/BlogList.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | // import ReactMarkdown from "react-markdown";
3 |
4 | import BlogSplash from "./BlogSplash";
5 |
6 | import styled from "styled-components";
7 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
8 | import { faPlus } from "@fortawesome/free-solid-svg-icons";
9 |
10 | const ListWrapper = styled.div`
11 | background: #fafafa;
12 | margin-bottom: 5vh;
13 | .list {
14 | width: 80%;
15 | margin: 0 auto;
16 | display: grid;
17 | grid-template-columns: repeat(3, 1fr);
18 | grid-column-gap: 2vw;
19 | padding: 0;
20 | grid-row-gap: 3vw;
21 | .article {
22 | box-shadow: 0 1px 7px rgba(0, 0, 0, 0.05);
23 | height: 100%;
24 | border: 1px solid rgba(0, 0, 0, 0.04) !important;
25 | display: flex;
26 | flex-direction: column;
27 | background: white;
28 | .hero_image img {
29 | width: 100%;
30 | height: 25vh;
31 | object-fit: cover;
32 | }
33 | .blog__info {
34 | padding: 4vh 2vw;
35 | display: flex;
36 | flex-direction: column;
37 | height: 100%;
38 | h2 {
39 | margin-top: 0;
40 | font-size: 3vh;
41 | margin-bottom: 7vh;
42 | }
43 | h3 {
44 | font-weight: normal;
45 | margin: 0;
46 | font-size: 2.6vh;
47 | }
48 | .date {
49 | font-weight: 300;
50 | }
51 | .meta {
52 | margin-top: auto;
53 | }
54 | }
55 | }
56 | .create {
57 | display: flex;
58 | justify-content: center;
59 | align-items: center;
60 | opacity: 0.5;
61 | transition: 0.5s;
62 | :hover {
63 | opacity: 1;
64 | }
65 | .outer-circle {
66 | background: #fafafa;
67 | padding: 3vw;
68 | border-radius: 50%;
69 | color: black;
70 | font-size: 1.5vw;
71 | }
72 | .create-post {
73 | font-weight: normal;
74 | }
75 | }
76 | }
77 | .article-meta {
78 | display: flex;
79 | align-items: center;
80 | }
81 | .author-image {
82 | display: flex;
83 | align-items: center;
84 | margin-right: 1vh;
85 | }
86 | .author-image img {
87 | height: 3vw;
88 | width: 3vw;
89 | border-radius: 50%;
90 | overflow: hidden;
91 | object-position: center;
92 | object-fit: cover;
93 | }
94 | .meta-info {
95 | display: flex;
96 | justify-content: center;
97 | flex-direction: column;
98 | }
99 | @media only screen and (min-width: 320px) and (max-width: 480px) {
100 | padding-bottom: 7vw;
101 | .list {
102 | grid-template-columns: repeat(1, 1fr);
103 | grid-row-gap: 7vw;
104 | margin-top: 7vw;
105 | .article {
106 | min-height: 40vh;
107 | }
108 | .outer-circle {
109 | padding: 10vw !important;
110 | font-size: 5vw !important;
111 | }
112 | .create {
113 | opacity: 1 !important;
114 | }
115 | .blog__info {
116 | padding: 4vh 6vw !important;
117 | h2 {
118 | font-size: 3.3vh !important;
119 | }
120 | }
121 | }
122 | .author-image img {
123 | height: 11vw;
124 | width: 11vw;
125 | }
126 | .article-meta {
127 | margin-top: 2vh;
128 | margin-left: 0vh;
129 | }
130 | }
131 | `;
132 |
133 | const BlogList = (props) => {
134 | function truncateSummary(content) {
135 | return content.slice(0, 140).trimEnd() + "...";
136 | }
137 |
138 | function reformatDate(fullDate) {
139 | const date = new Date(fullDate);
140 | const day = date.getDate();
141 | const month = date.toLocaleString("default", { month: "short" });
142 | const year = date.getFullYear();
143 | return `${day} ${month}, ${year}`;
144 | }
145 |
146 | const posts = props.allBlogs;
147 | return (
148 |
149 |
150 |
193 |
194 | );
195 | };
196 |
197 | export default BlogList;
198 |
--------------------------------------------------------------------------------
/src/components/blog/BlogSplash.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import styled from "styled-components";
4 |
5 | const SplashWrapper = styled.div`
6 | .title-container {
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | padding: 1% 17.5vw;
11 | }
12 | .title {
13 | font-size: 12vh;
14 | text-align: center;
15 | line-height: 110%;
16 | }
17 | @media only screen and (min-width: 320px) and (max-width: 480px) {
18 | .title {
19 | font-size: 10vw;
20 | line-height: 110%;
21 | }
22 | .title-container {
23 | padding: 1% 1vw;
24 | }
25 | }
26 | `;
27 |
28 | export default function BlogSplash() {
29 | return (
30 |
31 |
32 |
Today a reader, tomorrow a leader.
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/blog/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const FooterWrapper = styled.div`
5 | margin: 0 !important;
6 | padding: 3vh calc(5vw + 3vh);
7 | display: flex;
8 | padding: 3vh;
9 | font-size: 1.3vw;
10 | background: #2e2e2e;
11 | color: white;
12 | .footer-container {
13 | margin-left: auto;
14 | display: flex;
15 | }
16 | .footer-content {
17 | margin-right: 2rem;
18 | color: white;
19 | }
20 | @media only screen and (min-width: 320px) and (max-width: 480px) {
21 | margin: 0vh 1vw;
22 | font-size: 4vw;
23 | .mobile {
24 | display: none;
25 | }
26 | .footer-content {
27 | margin-right: 0rem;
28 | }
29 | }
30 | `;
31 |
32 | export default function Footer() {
33 | return (
34 |
35 | © 2024 ProjectLearn
36 |
52 |
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/dashboard/Content/Project.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Link from "next/link";
4 |
5 | import {
6 | faVideo,
7 | faNewspaper,
8 | faArrowCircleRight,
9 | } from "@fortawesome/free-solid-svg-icons";
10 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
11 |
12 | const ProjectWrapper = styled.div`
13 | .project-grid-items {
14 | height: 100%;
15 | display: flex;
16 | position: relative;
17 | border-radius: 1vh;
18 | flex-direction: column;
19 | padding: 1.1vw;
20 | transition: 0.4s;
21 | background: var(--themeDark);
22 | color: white;
23 | cursor: pointer;
24 | box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.075);
25 | border-top: 4px solid var(${(props) => props.bg}-alt);
26 | background: linear-gradient(
27 | 135deg,
28 | rgba(255, 255, 255, 0.05) 10.93%,
29 | rgba(255, 255, 255, 0) 90%
30 | );
31 | backdrop-filter: blur(100px);
32 | :hover {
33 | box-shadow: 0 1px 7px rgba(0, 0, 0, 0.1);
34 | background-color: var(--themeDark);
35 | transform: scale(1.1);
36 | .cat-art {
37 | background-color: var(${(props) => props.bg}-alt);
38 | //box-shadow: none !important;
39 | color: white;
40 | }
41 | .tag {
42 | background-color: linear-gradient(
43 | 135deg,
44 | rgba(255, 255, 255, 0.05) 10.93%,
45 | rgba(255, 255, 255, 0) 90%
46 | );
47 | //box-shadow: none !important;
48 | color: white;
49 | }
50 | .link {
51 | color: black;
52 | transform: translateX(-0.7vh);
53 | }
54 | }
55 | .details-1 {
56 | display: flex;
57 | .main-cat {
58 | display: flex;
59 | flex-direction: column;
60 | justify-content: center;
61 | align-items: center;
62 | margin-bottom: 3vh;
63 | .svg-inline--fa {
64 | font-size: 2vw;
65 | padding: 1vh;
66 | }
67 | .cat-art {
68 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px,
69 | rgba(0, 0, 0, 0.24) 0px 1px 2px;
70 | border-radius: 1vh;
71 | height: 4vw;
72 | width: 4vw;
73 | padding: 1.5vh;
74 | display: flex;
75 | flex-direction: column;
76 | justify-content: center;
77 | align-items: center;
78 | transition: 0.4s;
79 | background: linear-gradient(
80 | 135deg,
81 | rgba(255, 255, 255, 0.05) 10.93%,
82 | rgba(255, 255, 255, 0) 90%
83 | );
84 | box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.075);
85 | }
86 | .cat-title {
87 | margin-top: 0.5vh;
88 | font-size: 1vw;
89 | }
90 | }
91 | .date-added {
92 | margin-left: auto;
93 | display: flex;
94 | align-items: center;
95 | flex-direction: column;
96 | .section-title {
97 | font-size: 1vw;
98 | }
99 | .date-value {
100 | font-size: 1.2vw;
101 | }
102 | }
103 | }
104 | .details-2 {
105 | font-size: 1.3vw;
106 | margin: 0;
107 | margin-bottom: 2vh;
108 | font-weight: normal;
109 | }
110 | .details-3 {
111 | margin-top: auto;
112 | .section-title {
113 | font-size: 1.2vw;
114 | font-weight: 100;
115 | }
116 | .tags-container {
117 | margin-top: 1vh;
118 | display: flex;
119 | flex-wrap: wrap;
120 | row-gap: 1vh;
121 | min-width: 0;
122 | .tag {
123 | transition: 0.2s;
124 | cursor: pointer;
125 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
126 | //border: 1px solid var(${(props) => props.bg}-alt);
127 | //border: 1px dashed var(${(props) => props.bg}-alt);
128 | box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.075);
129 | border-radius: 0.5vh;
130 | width: fit-content;
131 | margin-right: 0.7vw;
132 | padding: 0.1vw 0.6vw;
133 | min-width: 0;
134 | text-overflow: ellipsis;
135 | white-space: nowrap;
136 | overflow: hidden;
137 | font-size: 1.1vw;
138 | }
139 | }
140 | }
141 | .link {
142 | position: absolute;
143 | font-size: 1.4vw;
144 | bottom: 1vh;
145 | right: 1vh;
146 | cursor: pointer;
147 | z-index: 10;
148 | transition: 0.2s;
149 | color: var(${(props) => props.bg}-alt);
150 | }
151 | }
152 | @media only screen and (max-width: 480px) {
153 | .project-grid-items {
154 | padding: 3vw;
155 | }
156 |
157 | .details-2 {
158 | font-size: 6vw !important;
159 | margin: 0;
160 | margin-bottom: 2vh;
161 | font-weight: normal;
162 | }
163 | .details-3 {
164 | .section-title {
165 | font-size: 4vw !important;
166 | margin-bottom: 1.5vh;
167 | }
168 | .tags-container {
169 | margin: 0 !important;
170 | line-height: 110% !important;
171 | .tag {
172 | margin-left: 1vw;
173 | padding: 0.7vw !important;
174 | cursor: pointer;
175 | font-size: 4vw !important;
176 | }
177 | }
178 | margin-bottom: 1vh;
179 | }
180 | .svg-inline--fa {
181 | font-size: 6vw !important;
182 | padding: 0vh !important;
183 | }
184 | .cat-title {
185 | font-size: 4.5vw !important;
186 | }
187 | .section-title {
188 | font-size: 4.5vw !important;
189 | }
190 | .date-value {
191 | font-size: 5vw !important;
192 | }
193 | .cat-art {
194 | width: 10vw !important;
195 | height: 10vw !important;
196 | }
197 | }
198 | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 1) {
199 | .project-grid-items {
200 | padding: 3vw;
201 | border-radius: 2vw;
202 | border-top: 5px solid var(${(props) => props.bg}-alt);
203 | }
204 |
205 | .details-2 {
206 | font-size: 4vw !important;
207 | margin: 0;
208 | margin-bottom: 3vh;
209 | font-weight: normal;
210 | }
211 | .details-3 {
212 | .section-title {
213 | font-size: 3vw !important;
214 | margin-bottom: 1.5vh;
215 | }
216 | .tags-container {
217 | margin: 0 !important;
218 | line-height: 110% !important;
219 | .tag {
220 | margin-left: 1vw;
221 | padding: 0.7vw !important;
222 | cursor: pointer;
223 | font-size: 3vw !important;
224 | }
225 | }
226 | margin-bottom: 1vh;
227 | }
228 | .svg-inline--fa {
229 | font-size: 4vw !important;
230 | padding: 0vh !important;
231 | }
232 | .cat-title {
233 | font-size: 3.2vw !important;
234 | }
235 | .section-title {
236 | font-size: 3vw !important;
237 | }
238 | .date-value {
239 | font-size: 3vw !important;
240 | }
241 | .cat-art {
242 | width: 8vw !important;
243 | height: 8vw !important;
244 | }
245 | }
246 | `;
247 |
248 | const PostLink = (props) => (
249 |
253 |
254 |
255 |
256 |
257 | );
258 |
259 | const Content = (props) => {
260 | const {
261 | id,
262 | type,
263 | title,
264 | category,
265 | subCategory,
266 | tech,
267 | description,
268 | source,
269 | datePublished,
270 | projectURL,
271 | submittedBy,
272 | } = props.project;
273 |
274 | const appIcon = (
275 |
276 | {type === "video" ? (
277 |
278 |
279 | Video
280 |
281 | ) : (
282 |
283 |
284 | Article
285 |
286 | )}
287 |
288 | );
289 |
290 | const date = new Date(datePublished); // 2009-11-10
291 | const day = date.getDate();
292 | const month = date.toLocaleString("default", { month: "short" });
293 | const year = date.getFullYear();
294 |
295 | let urlTitle = title.toLowerCase().split(" ").join("-");
296 | const background = props.color;
297 |
298 | return (
299 |
300 |
304 |
305 | {/*
*/}
306 | {/*
307 |
308 |
309 |
310 | */}
311 |
312 |
315 |
316 |
Date Published
317 |
{`${day} ${month} ${year}`}
318 |
319 |
320 |
{title}
321 |
322 | {/*
Top Technologies:
*/}
323 |
324 | {tech.slice(0, 2).map((t) => (
325 |
326 | {t}
327 |
328 | ))}
329 |
330 |
331 |
332 |
333 |
334 | {/* */}
335 |
336 | );
337 | };
338 |
339 | export default Content;
340 |
--------------------------------------------------------------------------------
/src/components/dashboard/Content/ProjectList.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import styled from "styled-components";
3 | import Project from "./Project";
4 | import { useRouter, withRouter } from "next/router";
5 |
6 | const ListWrapper = styled.div`
7 | .projects-grid {
8 | margin-left: 2vw;
9 | margin-right: 2vw;
10 | margin-top: 20vh;
11 | display: grid;
12 | padding-bottom: 4vh;
13 | grid-template-columns: 1fr 1fr 1fr 1fr;
14 | grid-column-gap: 2vw;
15 | grid-row-gap: 4vh;
16 | margin-bottom: 5vh;
17 | @media only screen and (max-width: 480px) {
18 | grid-template-columns: 1fr;
19 | grid-row-gap: 6vh;
20 | margin-left: 11.5%;
21 | margin-right: 11.5%;
22 | grid-column-gap: 2vh;
23 | margin-top: 28.5vh;
24 | }
25 | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 1) {
26 | grid-template-columns: 1fr 1fr;
27 | grid-row-gap: 8vw;
28 | margin-left: 2.5%;
29 | margin-right: 2.5%;
30 | grid-column-gap: 4vw;
31 | margin-top: 28.5vh;
32 | }
33 | }
34 | `;
35 |
36 | const ProjectList = (props) => {
37 | const { projects } = props;
38 | const {
39 | query: { tech },
40 | } = useRouter();
41 | return (
42 |
43 |
44 |
45 | {projects != null
46 | ? tech !== undefined
47 | ? projects
48 | .filter((project) => project.tech.includes(tech))
49 | .map((project) => (
50 |
56 | ))
57 | : projects.map((project) => (
58 |
64 | ))
65 | : null}
66 |
67 |
68 |
69 | );
70 | };
71 |
72 | export default ProjectList;
73 |
--------------------------------------------------------------------------------
/src/components/dashboard/Content/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import styled from "styled-components";
3 |
4 | import ProjectList from "./ProjectList";
5 |
6 | //Redux Stuff
7 | import { connect } from "react-redux";
8 | import { useRouter, withRouter } from "next/router";
9 |
10 | import Link from "next/link";
11 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
12 | import { faArrowLeft } from "@fortawesome/free-solid-svg-icons";
13 |
14 | const ContentWrapper = styled.div`
15 | display: grid;
16 | width: auto;
17 | min-height: 100vh;
18 | grid-template-rows: auto auto;
19 | .dummy {
20 | position: fixed;
21 | z-index: 0;
22 | height: 30vh;
23 | width: 80vw;
24 | background: linear-gradient(
25 | 90deg,
26 | var(${(props) => props.bg}),
27 | var(${(props) => props.bg}-alt)
28 | );
29 | backdrop-filter: blur(10px);
30 | }
31 | .section-header {
32 | display: flex;
33 | background: linear-gradient(
34 | 90deg,
35 | var(${(props) => props.bg}),
36 | var(${(props) => props.bg}-alt)
37 | );
38 | backdrop-filter: blur(10px);
39 | color: ${(props) => props.color};
40 | position: fixed;
41 | z-index: 100;
42 | width: 79vw;
43 | padding-bottom: 4vh;
44 | .section-title {
45 | font-size: 2vw;
46 | padding: 4vh 4vh 0 4vh;
47 | font-weight: normal;
48 | margin: 0;
49 | }
50 | .section-sub-title {
51 | padding: 1vh 4vh;
52 | font-weight: 100;
53 | font-size: 2.7vh;
54 | }
55 | }
56 | .search-container {
57 | margin-top: 5vh;
58 | margin-right: 5vh;
59 | margin-left: auto;
60 | .search-bar {
61 | padding: 1vh;
62 | font-size: 1.3vw;
63 | border-radius: 3vh;
64 | border: none;
65 | padding-left: 2.5vh;
66 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px,
67 | rgba(0, 0, 0, 0.24) 0px 1px 2px;
68 | outline: none;
69 | }
70 | }
71 | .mobile-title {
72 | display: none;
73 | }
74 | .back-to-landing,
75 | .mobile-filter-reset {
76 | display: none;
77 | }
78 | @media only screen and (max-width: 480px) {
79 | .headers {
80 | display: none;
81 | }
82 | .section-header {
83 | position: fixed;
84 | width: 100vw;
85 | padding-bottom: 3vh;
86 | }
87 | .dummy {
88 | width: 100vw;
89 | height: 35vh;
90 | }
91 | .search-container {
92 | display: flex;
93 | flex-direction: column;
94 | align-items: center;
95 | margin: 5vh auto;
96 | margin-bottom: 0;
97 | }
98 | .mobile-title {
99 | display: block;
100 | display: flex;
101 | justify-content: center;
102 | font-size: 7vw;
103 | margin-bottom: 2vh;
104 | }
105 | .mobile-filter-reset {
106 | display: flex;
107 | align-items: center;
108 | font-size: 7vw;
109 | margin-top: 3vh;
110 | .cat-title {
111 | background: var(${(props) => props.bg}-alt);
112 | padding: 1vh;
113 | border-radius: 1vh;
114 | font-size: 7vw;
115 | }
116 | .clear {
117 | text-decoration: underline;
118 | margin-left: 2vw;
119 | }
120 | }
121 | .search-bar {
122 | font-size: 2.5vh !important;
123 | }
124 | .back-to-landing {
125 | display: block;
126 | position: absolute;
127 | top: 1.5vh;
128 | left: 2vh;
129 | font-size: 3vh;
130 | cursor: pointer;
131 | z-index: 110;
132 | }
133 | }
134 | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 1) {
135 | .headers {
136 | display: none;
137 | }
138 | .section-header {
139 | position: fixed;
140 | width: 100vw;
141 | padding-bottom: 3vh;
142 | }
143 | .dummy {
144 | width: 100vw;
145 | height: 35vh;
146 | }
147 | .search-container {
148 | display: flex;
149 | flex-direction: column;
150 | align-items: center;
151 | margin: 5vh auto;
152 | margin-bottom: 0;
153 | }
154 | .mobile-title {
155 | display: block;
156 | display: flex;
157 | justify-content: center;
158 | font-size: 3.5vh;
159 | margin-bottom: 2vh;
160 | }
161 | .mobile-filter-reset {
162 | display: flex;
163 | align-items: center;
164 | font-size: 3.5vh;
165 | margin-top: 3vh;
166 | .cat-title {
167 | background: var(${(props) => props.bg}-alt);
168 | padding: 1vh;
169 | border-radius: 1vh;
170 | font-size: 3.5vh;
171 | }
172 | .clear {
173 | text-decoration: underline;
174 | margin-left: 2vw;
175 | }
176 | }
177 | .search-bar {
178 | font-size: 2.5vh !important;
179 | }
180 | .back-to-landing {
181 | display: block;
182 | position: absolute;
183 | top: 1.5vh;
184 | left: 2vh;
185 | font-size: 3vh;
186 | cursor: pointer;
187 | z-index: 110;
188 | }
189 | }
190 | `;
191 |
192 | class Content extends Component {
193 | state = {
194 | search: "",
195 | };
196 |
197 | updateSearch = (event) => {
198 | event.preventDefault();
199 | const query = event.target.value;
200 | this.setState(() => {
201 | return {
202 | search: query,
203 | };
204 | });
205 | };
206 |
207 | resetSearch = () => {
208 | this.setState(() => {
209 | return {
210 | search: "",
211 | };
212 | });
213 | };
214 |
215 | render() {
216 | const {
217 | query: { tech },
218 | } = this.props.router;
219 |
220 | const categorySlug = this.props.slug;
221 | const categoryTitle = this.props.title;
222 | const url = this.props.url;
223 |
224 | let { projects } = this.props;
225 |
226 | if(!projects){
227 | return null
228 | }
229 |
230 | projects = projects
231 | .filter((project) => project.category.includes(categorySlug))
232 | .map((item) => item);
233 |
234 | if (this.state.search != "") {
235 | projects = projects
236 | .filter((project) => {
237 | return (
238 | project.title
239 | .toLowerCase()
240 | .indexOf(this.state.search.toLowerCase()) !== -1 ||
241 | project.tech.some((t) => {
242 | return (
243 | t.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
244 | );
245 | }) === true
246 | );
247 | })
248 | .map((item) => item);
249 | }
250 |
251 | return (
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
{categoryTitle}
262 |
263 | {projects ? projects.length : null} Projects
264 |
265 |
266 |
267 |
268 |
{categoryTitle}
269 |
276 |
277 |
this.resetSearch()}
280 | >
281 |
282 |
{tech || "Showing All"}
283 |
284 |
Clear Filter
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 | );
293 | }
294 | }
295 |
296 | const mapStateToProps = (state) => {
297 | return {
298 | projects: state.data.projects,
299 | };
300 | };
301 |
302 | export default connect(mapStateToProps, {})(withRouter(Content));
303 |
--------------------------------------------------------------------------------
/src/components/dashboard/Info/RelatedProject.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import styled from "styled-components";
3 | import Link from "next/link";
4 |
5 | import {
6 | faVideo,
7 | faNewspaper,
8 | faArrowCircleRight,
9 | } from "@fortawesome/free-solid-svg-icons";
10 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
11 |
12 | const ProjectWrapper = styled.div`
13 | margin-bottom: 4vh;
14 | .project-grid-items {
15 | display: flex;
16 | position: relative;
17 | border-radius: 1vh;
18 | flex-direction: column;
19 | padding: 1.1vw;
20 | background-color: white;
21 | transition: 0.4s;
22 | border-top: 4px solid var(--dashboard-purple-alt);
23 | box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.075);
24 | border-top: 4px solid var(${(props) => props.bg}-alt);
25 | background: linear-gradient(
26 | 135deg,
27 | rgba(255, 255, 255, 0.05) 10.93%,
28 | rgba(255, 255, 255, 0) 90%
29 | );
30 | cursor: pointer;
31 | :hover {
32 | transform: scale(1.1);
33 | color: white;
34 | .cat-art {
35 | //background-color: var(--dashboard-purple-alt);
36 | //box-shadow: none !important;
37 | }
38 | .tag {
39 | //background-color: var(--dashboard-purple-alt);
40 | //box-shadow: none !important;
41 | }
42 | .link {
43 | color: white;
44 | }
45 | }
46 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px;
47 | .details-1 {
48 | display: flex;
49 | .main-cat {
50 | display: flex;
51 | flex-direction: column;
52 | justify-content: center;
53 | align-items: center;
54 | margin-bottom: 3vh;
55 | .svg-inline--fa {
56 | font-size: 2vw;
57 | padding: 1vh;
58 | }
59 | .cat-art {
60 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px,
61 | rgba(0, 0, 0, 0.24) 0px 1px 2px;
62 | border-radius: 1vh;
63 | height: 4vw;
64 | width: 4vw;
65 | padding: 1.5vh;
66 | display: flex;
67 | flex-direction: column;
68 | justify-content: center;
69 | align-items: center;
70 | background: linear-gradient(
71 | 135deg,
72 | rgba(255, 255, 255, 0.05) 10.93%,
73 | rgba(255, 255, 255, 0) 90%
74 | );
75 | box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.075);
76 | border-top: 4px solid var(${(props) => props.bg}-alt);
77 | }
78 | .cat-title {
79 | margin-top: 0.5vh;
80 | font-size: 1vw;
81 | }
82 | }
83 | .date-added {
84 | margin-left: auto;
85 | display: flex;
86 | align-items: center;
87 | flex-direction: column;
88 | .section-title {
89 | font-size: 1vw;
90 | height: fit-content;
91 | padding: 0;
92 | }
93 | .date-value {
94 | font-size: 1.2vw;
95 | }
96 | }
97 | }
98 | .details-2 {
99 | font-size: 1.5vw;
100 | margin: 0;
101 | margin-bottom: 3vh;
102 | font-weight: normal;
103 | }
104 | .details-3 {
105 | margin-top: auto;
106 | .section-title {
107 | font-size: 1.2vw;
108 | font-weight: 100;
109 | height: fit-content;
110 | padding: 0;
111 | }
112 | .tags-container {
113 | margin-top: 1vh;
114 | flex-flow: row wrap;
115 | line-height: 170%;
116 | word-wrap: normal;
117 | .tag {
118 | cursor: pointer;
119 | //border: 1px dashed var(--dashboard-purple-alt);
120 | width: fit-content;
121 | margin-right: 0.7vw;
122 | padding: 0.1vw 0.6vw;
123 | border-radius: 1vh;
124 | font-size: 1.2vw;
125 | box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.075);
126 | }
127 | }
128 | }
129 | .link {
130 | position: absolute;
131 | font-size: 1.4vw;
132 | bottom: 1vh;
133 | right: 1vh;
134 | cursor: pointer;
135 | z-index: 10;
136 | }
137 | }
138 | `;
139 |
140 | const PostLink = (props) => (
141 |
145 |
146 |
147 |
148 |
149 | );
150 |
151 | export default class Content extends Component {
152 | render() {
153 | const {
154 | id,
155 | type,
156 | title,
157 | category,
158 | subCategory,
159 | tech,
160 | description,
161 | source,
162 | datePublished,
163 | projectURL,
164 | submittedBy,
165 | } = this.props.project;
166 |
167 | const appIcon = (
168 |
169 | {type === "video" ? (
170 |
171 |
172 | Video
173 |
174 | ) : (
175 |
176 |
177 | Article
178 |
179 | )}
180 |
181 | );
182 |
183 | const date = new Date(datePublished); // 2009-11-10
184 | const day = date.getDate();
185 | const month = date.toLocaleString("default", { month: "short" });
186 | const year = date.getFullYear();
187 |
188 | let urlTitle = title.toLowerCase().split(" ").join("-");
189 |
190 | return (
191 |
192 |
196 |
197 |
198 |
199 |
200 |
203 |
204 |
Date Published
205 |
{`${day} ${month} ${year}`}
206 |
207 |
208 |
{title}
209 |
210 |
Top Technologies:
211 |
212 | {tech.slice(0, 3).map((t) => (
213 |
220 | {t}
221 |
222 | ))}
223 |
224 |
225 |
226 |
227 |
228 | {/* */}
229 |
230 | );
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/components/dashboard/Info/RelatedProjects.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Project from "./RelatedProject";
4 |
5 | const ListWrapper = styled.div`
6 | min-height: 100vh;
7 | overflow-y: auto;
8 | .projects-grid {
9 | margin-left: 3vw;
10 | margin-right: 3vw;
11 |
12 | display: grid;
13 | grid-template-rows: 1fr 1fr 1fr;
14 | align-items: center;
15 |
16 | height: 85vh;
17 | }
18 | .section-title {
19 | height: 12vh;
20 | display: flex;
21 | align-items: center;
22 | font-size: 4.5vh;
23 | padding-left: 3vw;
24 | }
25 | `;
26 |
27 | const ProjectList = props => {
28 | const { projects } = props;
29 | return (
30 |
31 | More Projects
32 |
33 |
34 | {projects != null
35 | ? projects.map(project => (
36 |
37 | ))
38 | : null}
39 |
40 |
41 | );
42 | };
43 |
44 | export default ProjectList;
45 |
--------------------------------------------------------------------------------
/src/components/dashboard/Layout.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import styled from "styled-components";
3 | import Sidebar from "./Sidebar";
4 | import Content from "./Content";
5 |
6 | const LayoutWrapper = styled.div`
7 | display: grid;
8 | grid-template-columns: 20vw auto;
9 | background-color: var(--themeDark);
10 | @media only screen and (max-width: 480px) {
11 | grid-template-columns: 1fr;
12 | .layout-first {
13 | display: none;
14 | }
15 | }
16 | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 1) {
17 | grid-template-columns: 1fr;
18 | .layout-first {
19 | display: none;
20 | }
21 | }
22 | `;
23 |
24 | export default class Layout extends Component {
25 | render() {
26 | return (
27 |
28 |
29 |
34 |
35 |
36 |
42 |
43 |
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/dashboard/Sidebar/Categories.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import styled from "styled-components";
3 | import { webDev, gameDev, mobDev, mlAI } from "../../../utils/technologies";
4 | import Link from "next/link";
5 | import { useRouter, withRouter } from "next/router";
6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7 | import { library } from "@fortawesome/fontawesome-svg-core";
8 | import {
9 | faHtml5,
10 | faCss3,
11 | faJs,
12 | faAngular,
13 | faNodeJs,
14 | faVuejs,
15 | faCuttlefish,
16 | faMicrosoft,
17 | faPython,
18 | faGoogle,
19 | faKickstarterK,
20 | faAndroid,
21 | faApple,
22 | faJava,
23 | faSwift,
24 | faReact,
25 | faUnity,
26 | } from "@fortawesome/free-brands-svg-icons";
27 | import { faInfinity, faCode } from "@fortawesome/free-solid-svg-icons";
28 | library.add(
29 | faHtml5,
30 | faCss3,
31 | faJs,
32 | faAngular,
33 | faNodeJs,
34 | faVuejs,
35 | faCuttlefish,
36 | faMicrosoft,
37 | faPython,
38 | faGoogle,
39 | faKickstarterK,
40 | faAndroid,
41 | faApple,
42 | faJava,
43 | faSwift,
44 | faReact,
45 | faUnity
46 | );
47 |
48 | const CategoryWrapper = styled.div`
49 | .cats {
50 | height: 100%;
51 | width: 100%;
52 | display: flex;
53 | flex-direction: column;
54 | align-items: center;
55 | .section-title {
56 | font-size: 2vw;
57 | padding: 3vh;
58 | color: white;
59 | margin-top: 1vh;
60 | }
61 | .cat-grid {
62 | display: grid;
63 | grid-template-columns: 1fr 1fr;
64 | width: 100%;
65 | justify-items: center;
66 | text-align: center;
67 | color: var(--theme-grey);
68 | overflow-y: scroll;
69 | }
70 | .cat-grid-item {
71 | font-size: 2vw;
72 | height: 5.3vw;
73 | width: 5.3vw;
74 | border-radius: 2vh;
75 | display: flex;
76 | flex-direction: column;
77 | justify-content: center;
78 | align-items: center;
79 | cursor: pointer;
80 | color: white;
81 | /* background-color: var(${(props) => props.bg}-alt); */
82 | padding: 1.2vw;
83 | :hover {
84 | color: var(${(props) => props.bg}-alt);
85 | transition: 0.4s;
86 | }
87 | }
88 | .cat-grid-item-selected {
89 | font-size: 2.8vw;
90 | height: 5.3vw;
91 | width: 5.3vw;
92 | border-radius: 2vh;
93 | display: flex;
94 | flex-direction: column;
95 | justify-content: center;
96 | align-items: center;
97 | cursor: pointer;
98 | padding: 1.2vw;
99 | color: var(${(props) => props.bg}-alt);
100 | }
101 | .cat-title {
102 | margin-top: 1vh;
103 | font-size: 1.2vw;
104 | }
105 | }
106 | `;
107 | const Categories = (props) => {
108 | const {
109 | query: { tech },
110 | } = useRouter();
111 |
112 | const router = useRouter();
113 |
114 | const techCategories =
115 | props.slug === "web-dev"
116 | ? webDev
117 | : props.slug === "mob-dev"
118 | ? mobDev
119 | : props.slug === "game-dev"
120 | ? gameDev
121 | : mlAI;
122 |
123 | const removeQueryParam = (param) => {
124 | const { pathname, query } = router;
125 | const params = new URLSearchParams(query);
126 | params.delete(param);
127 | router.replace({ pathname, query: params.toString() }, undefined, {
128 | shallow: true,
129 | });
130 | };
131 |
132 | return (
133 |
134 |
135 |
Technologies
136 |
137 |
{
139 | removeQueryParam("tech");
140 | }}
141 | >
142 | {tech != null ? (
143 |
144 |
145 |
Show All
146 |
147 | ) : (
148 |
149 |
150 |
Show All
151 |
152 | )}
153 |
154 | {techCategories.map((item, index) =>
155 | tech === item.category ? (
156 |
157 | {item.icon ? (
158 |
159 | ) : (
160 |
161 | )}
162 |
{item.category}
163 |
164 | ) : (
165 |
171 |
172 | {item.icon ? (
173 |
174 | ) : (
175 |
176 | )}
177 |
{item.category}
178 |
179 |
180 | )
181 | )}
182 |
183 |
184 |
185 | );
186 | };
187 |
188 | export default Categories;
189 |
--------------------------------------------------------------------------------
/src/components/dashboard/Sidebar/CategoryInfo.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import styled from "styled-components";
3 | import Link from "next/link";
4 | import { useRouter, withRouter } from "next/router";
5 | import { connect } from "react-redux";
6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7 | import { faArrowLeft } from "@fortawesome/free-solid-svg-icons";
8 |
9 | const InfoWrapper = styled.div`
10 | width: 20vw;
11 | .cat-info {
12 | background: var(${(props) => props.bg});
13 | position: relative;
14 | display: flex;
15 | height: 100%;
16 | flex-direction: column;
17 | justify-content: center;
18 | align-items: center;
19 | color: var(${(props) => props.color});
20 | }
21 | .selected-cat {
22 | display: flex;
23 | flex-direction: column;
24 | justify-content: center;
25 | align-items: center;
26 | margin-bottom: 3vh;
27 | color: white;
28 | .svg-inline--fa {
29 | font-size: 7vh;
30 | padding: 1vh;
31 | }
32 | .cat-art {
33 | background: var(${(props) => props.bg}-alt);
34 | border-radius: 2vh;
35 | height: 9vh;
36 | width: 9vh;
37 | display: flex;
38 | justify-content: center;
39 | align-items: center;
40 | }
41 | .cat-title {
42 | margin-top: 1vh;
43 | font-size: 1.8vw;
44 | background: var(${(props) => props.bg}-alt);
45 | padding: 1vh;
46 | border-radius: 1vh;
47 | }
48 | }
49 | .selected-cat-stats {
50 | display: grid;
51 | width: 100%;
52 | grid-template-columns: 1fr 1fr 1fr;
53 | justify-items: center;
54 | text-align: center;
55 | .stat {
56 | .stat-title {
57 | font-size: 1.3vw;
58 | }
59 | .stat-sub-title {
60 | font-size: 1.2vw;
61 | font-weight: 100;
62 | }
63 | }
64 | }
65 | .back-to-landing {
66 | position: absolute;
67 | top: 1.5vh;
68 | left: 2vh;
69 | font-size: 4vh;
70 | cursor: pointer;
71 | }
72 | @media only screen and (min-width: 320px) and (max-width: 480px) {
73 | .back-to-landing {
74 | font-size: 2.5vh;
75 | }
76 | }
77 | `;
78 |
79 | const CategoryInfo = (props) => {
80 | const {
81 | query: { tech },
82 | } = useRouter();
83 |
84 | let { projects } = props;
85 |
86 | let totalCount = 0,
87 | articleCount = 0,
88 | videoCount = 0;
89 |
90 | const categorySlug = props.slug;
91 |
92 | if (projects != null) {
93 | projects = projects
94 | .filter((project) => project.category.includes(categorySlug))
95 | .map((item) => item);
96 | if (tech != undefined) {
97 | const filteredProjects = projects
98 | .filter((projects) => projects.tech.includes(tech))
99 | .map((item) => item);
100 | totalCount = filteredProjects.length;
101 | articleCount = filteredProjects.filter((project) => project.type === "article").length;
102 | videoCount = totalCount - articleCount;
103 | } else {
104 | totalCount = projects.length;
105 | articleCount = projects.filter((project) => project.type === "article").length;
106 | videoCount = totalCount - articleCount;
107 | }
108 | }
109 |
110 | return (
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
{tech || "Showing All"}
120 |
121 |
122 |
123 |
Total
124 |
{totalCount}
125 |
126 |
127 |
Videos
128 |
{videoCount}
129 |
130 |
131 |
Articles
132 |
{articleCount}
133 |
134 |
135 |
136 |
137 | );
138 | };
139 | const mapStateToProps = (state) => {
140 | return {
141 | projects: state.data.projects,
142 | };
143 | };
144 |
145 | export default connect(mapStateToProps, {})(CategoryInfo);
146 |
--------------------------------------------------------------------------------
/src/components/dashboard/Sidebar/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import styled from "styled-components";
3 |
4 | import nameCheapLogo from "../../../images/powered-by-namecheap-black.png"
5 |
6 | import CategoryInfo from "./CategoryInfo";
7 | import Categories from "./Categories";
8 |
9 | const SidebarWrapper = styled.div`
10 | display: grid;
11 | position: fixed;
12 | left: 0;
13 | grid-template-rows: 30vh 60vh 10vh;
14 | width: 20vw;
15 | .footer {
16 | display: flex;
17 | padding-left: 3vh;
18 | font-size: 1.1vw;
19 | color: var(--theme-grey);
20 | align-items: center;
21 | justify-content: center;
22 | flex-direction: column;
23 | gap: 16px;
24 | a{width: 50%}
25 | }
26 | `;
27 |
28 | export default class Sidebar extends Component {
29 | render() {
30 | return (
31 |
32 |
33 |
34 | © 2024 ProjectLearn
39 |
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/landing/Categories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Link from "next/link";
3 | import styled from "styled-components";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 | import { faLongArrowAltRight } from "@fortawesome/free-solid-svg-icons";
6 | import Image from "next/image";
7 |
8 | import mobDev from "../../images/mob-dev-cat.png";
9 | import webDev from "../../images/prog-lang-cat.png";
10 | import ml from "../../images/ml-cat-alt.png";
11 | import gameDev from "../../images/game-dev-cat.png";
12 |
13 | const CategoriesWrapper = styled.div`
14 | margin: 10vh 5vw;
15 | display: grid;
16 | grid-template-columns: 9.5vw auto;
17 | grid-template-rows: 1fr;
18 | height: fit-content;
19 | .section-title-container {
20 | position: relative;
21 | display: flex;
22 | }
23 | .section-title {
24 | writing-mode: vertical-rl;
25 | transform: rotate(180deg);
26 | padding: 15vh 3vh;
27 | font-size: 4vh;
28 | background: rgba(255, 255, 255, 0.05);
29 | border-radius: 1vw;
30 | margin: 0;
31 | text-align: center;
32 | color: white;
33 | /* background-image: repeating-linear-gradient(45deg,#f9d9eb,#f9d9eb 50px,#f7cfe2 0,#f7cfe2 100px); */
34 | }
35 | .section-content {
36 | display: grid;
37 | grid-template-rows: 1fr 1fr;
38 | grid-template-columns: 1fr 1fr;
39 | grid-row-gap: 1rem;
40 | grid-column-gap: 1rem;
41 | }
42 | .section-category {
43 | display: grid;
44 | grid-template-columns: 1fr 2fr;
45 | align-content: center;
46 | position: relative;
47 | align-items: center;
48 | cursor: pointer;
49 | background: linear-gradient(135deg,rgba(255,255,255,0.05) 10.93%,rgba(255,255,255,0) 90%);
50 | padding: 1.3vh 1vh 1.3vh;
51 | color: white;
52 | border-radius: 15px;
53 | position: relative;
54 | background-image:repeating-linear-gradient(135deg,rgba(255,255,255,0.05) 0%,rgba(255,255,255,0) 90%);
55 | }
56 |
57 | .bounding-box{
58 | width: 25px;
59 | height: 25px;
60 | position: absolute;
61 | top: -15px;
62 | left: -15px;
63 | border: 1px solid #40a0ff;
64 | background-color: var(--themeDark);
65 | z-index: 2;
66 | opacity: 1;
67 | }
68 |
69 | .section-category:hover {
70 | .cat-redirect {
71 | transform: translateX(-0.7vh);
72 | }
73 | }
74 | .category-art {
75 | border-radius: 1vh;
76 | width: 14vw;
77 | display: flex;
78 | height: fit-content;
79 | justify-content: center;
80 | }
81 | .cat-img {
82 | height: 10vw;
83 | }
84 | .category-details {
85 | display: flex;
86 | padding: 0.5rem;
87 | flex-direction: column;
88 | justify-content: center;
89 | }
90 | .cat-title {
91 | font-size: 3vh;
92 | margin: 0;
93 | font-family: "Lato";
94 | margin-bottom: 2vh;
95 | }
96 | .cat-subtitle {
97 | font-size: 2.5vh;
98 | padding-right: 0.7vw;
99 | color: var(--themeTextSecondaryDark);
100 | }
101 | .cat-redirect {
102 | position: absolute;
103 | display: flex;
104 | align-items: center;
105 | bottom: 1.5vh;
106 | right: 2vh;
107 | font-size: 1.4vw;
108 | cursor: pointer;
109 | transition: 0.3s;
110 | }
111 | .cat-redirect-text {
112 | font-size: 2.8vh;
113 | margin-right: 1vh;
114 | }
115 | .rainbow-text{
116 | background: linear-gradient(90deg,#4ca5ff 2.34%,#b673f8 100.78%);
117 | -webkit-background-clip: text;
118 | -webkit-text-fill-color: transparent;
119 | text-fill-color: transparent;
120 | background-clip: text;
121 | font-weight: bold;
122 | }
123 | @media only screen and (max-width: 768px) {
124 | margin: 5vh 5vw;
125 | grid-template-columns: 1fr;
126 | .section-content {
127 | grid-template-rows: 1fr 1fr 1fr 1fr;
128 | grid-template-columns: 1fr;
129 | }
130 | .section-category {
131 | padding: 2vw;
132 | grid-template-columns: 0.8fr 2fr !important;
133 | align-items: center;
134 | }
135 | .section-title-container {
136 | display: none;
137 | }
138 | .cat-title {
139 | font-size: 5.3vw;
140 | margin-bottom: 1vh;
141 | }
142 | .cat-subtitle {
143 | font-size: 3.8vw;
144 | }
145 | .cat-img {
146 | height: 20vw;
147 | }
148 | .category-art {
149 | width: 25vw;
150 | display: flex;
151 | }
152 | .cat-redirect {
153 | font-size: 4vw;
154 | }
155 | }
156 | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 1) {
157 | margin: 5vh 5vw;
158 | grid-template-columns: 1fr;
159 | .section-content {
160 | grid-template-rows: 1fr 1fr 1fr 1fr;
161 | grid-template-columns: 1fr;
162 | }
163 | .section-category {
164 | padding: 2vw;
165 | grid-template-columns: 0.8fr 2fr !important;
166 | align-items: center;
167 | }
168 | .section-title-container {
169 | display: none;
170 | }
171 | .cat-title {
172 | font-size: 5vw;
173 | margin-bottom: 1vh;
174 | }
175 | .cat-subtitle {
176 | font-size: 3.8vw;
177 | }
178 | .cat-img {
179 | height: 20vw;
180 | }
181 | .category-art {
182 | width: 25vw;
183 | display: flex;
184 | }
185 | .cat-redirect {
186 | font-size: 4vw;
187 | }
188 | }
189 | `;
190 |
191 | export default function Categories() {
192 | return (
193 |
194 |
195 |
Categories
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
Web Development
205 |
206 | Build web applications using HTML, CSS, JavaScript, React and more.
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
Mobile Development
221 |
222 | Build mobile applications using Android, Flutter, React Native and more.
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
Game Development
237 |
238 | Build awesome video games. C#, PyGame, OpenGL, Unity and more.
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
Machine Learning
253 |
254 | Learn how a machine learns. Python, NumPy, Pandas, SciKit, Tensorflow and more.
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 | );
265 | }
266 |
--------------------------------------------------------------------------------
/src/components/landing/Features.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import codeIt from "../images/coding.png"
4 | import challengeYourself from "../images/challenge.png"
5 | import showcase from "../images/rocket.png"
6 |
7 | const FeaturesWrapper = styled.div`
8 | margin: 10vh 5vw;
9 | display: grid;
10 | grid-template-columns: 1fr 1fr 1fr;
11 | .feature-container{
12 | display: flex;
13 | align-items: center;
14 | flex-direction: column;
15 | }
16 | .feature-title{
17 | font-size: 4vh;
18 | margin-bottom: 2vh;
19 | }
20 | .feature-description{
21 | text-align: center;
22 | font-size: 2.8vh;
23 | }
24 | .feature-image{
25 | width: 8vw;
26 | margin-bottom: 4vh;
27 | }
28 | `;
29 |
30 | export default function Features() {
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
Don't just watch, do it
38 |
39 | The only way to actually learn something is to practice doing it.
40 |
41 |
42 |
43 |
44 |
45 |
46 |
Challenge yourself
47 |
48 | This approach
49 | creates a need to know essential content and skills.
50 |
51 |
52 |
53 |
54 |
55 |
56 |
Showcase your skills
57 |
58 | Give your own personal touch and showcase your projects in your
59 | resume or portfolio.
60 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/landing/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import nameCheapLogo from "../../images/powered-by-namecheap.png"
5 |
6 | const FooterWrapper = styled.div`
7 | margin: 0vh 5vw;
8 | display: flex;
9 | padding: 3vh;
10 | font-size: 1.3vw;
11 | color: var(--themeTextSecondaryDark);
12 | a{
13 | display: flex;
14 | align-items: center;
15 | }
16 |
17 | .footer-sponsor{
18 | display: flex;
19 | align-items: center;
20 | gap: 16px;
21 | }
22 | .footer-container {
23 | margin-left: auto;
24 | display: flex;
25 | a{
26 | display: flex;
27 | align-items: center;
28 | }
29 | }
30 | .footer-content {
31 | margin-right: 2rem;
32 | color: var(--themeTextSecondaryDark);
33 |
34 | }
35 | .img-link{
36 | width: 11.5%;
37 | }
38 | @media only screen and (min-width: 320px) and (max-width: 480px) {
39 | margin: 0vh 1vw;
40 | font-size: 4vw;
41 | flex-direction: column;
42 | gap: 16px;
43 | align-items: center;
44 |
45 | .footer-container {
46 | display: none;
47 | }
48 |
49 | .img-link{
50 | width: 50%;
51 | }
52 | .mobile {
53 | display: none;
54 | }
55 | .img-link{
56 | display: flex;
57 | justify-content: center;
58 | }
59 | .footer-content {
60 | dis
61 | margin-right: 0rem;
62 | margin-left: 3.5vw;
63 | }
64 | }
65 | `;
66 |
67 | export default function Footer() {
68 | return (
69 |
70 | © 2024 ProjectLearn
71 |
72 |
96 |
101 |
102 | );
103 | }
104 |
--------------------------------------------------------------------------------
/src/components/landing/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import Link from "next/link";
3 | import styled from "styled-components";
4 |
5 | import Circle from "../ui/loading-spinner";
6 |
7 | //Redux Stuff
8 | import { connect } from "react-redux";
9 | import { getGitHubStars } from "../../redux/actions/dataActions";
10 |
11 | //Icons and Images
12 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
13 | import { faGithub } from "@fortawesome/free-brands-svg-icons";
14 | import { faSearch, faHome, faPenNib } from "@fortawesome/free-solid-svg-icons";
15 |
16 | const NavWrapper = styled.div`
17 | margin: 0vh 0vw;
18 | display: grid;
19 | color: white;
20 | grid-template-rows: 1fr;
21 | grid-template-columns: auto auto;
22 | padding: 4vh 5vw;
23 | .nav-components-container {
24 | display: flex;
25 | margin-left: auto;
26 | align-items: center;
27 | }
28 | .nav-component {
29 | display: flex;
30 | align-items: center;
31 | margin-left: 2em;
32 | font-size: 3vh;
33 | .svg-inline--fa {
34 | font-size: 1.4em;
35 | margin-right: 0.2em;
36 | }
37 | cursor: pointer;
38 | }
39 | .gt-container {
40 | display: flex;
41 | flex-direction: row;
42 | background: linear-gradient(135deg,rgba(255,255,255,0.05) 10.93%,rgba(255,255,255,0) 90%);
43 | padding: 30px 35px;
44 | color: white;
45 | border-radius: 4vh;
46 | padding: 0rem 0.8rem 0rem 0;
47 | .svg-inline--fa {
48 | font-size: 1.8em;
49 | }
50 | }
51 | .pl-branding {
52 | display: flex;
53 | align-items: center;
54 | }
55 | .pl-logo {
56 | height: 3rem;
57 | padding: 1rem;
58 | }
59 | .pl-title {
60 | font-size: 5vh;
61 | margin: 0;
62 | }
63 | .mobile-nav {
64 | display: none;
65 | }
66 | .rainbow-text {
67 | background: linear-gradient(90deg, #4ca5ff 2.34%, #b673f8 100.78%);
68 | -webkit-background-clip: text;
69 | -webkit-text-fill-color: transparent;
70 | text-fill-color: transparent;
71 | background-clip: text;
72 | font-weight: bold;
73 | }
74 | @media only screen and (max-width: 768px) {
75 | padding: 2vh;
76 | grid-template-columns: 1fr;
77 | .nav-components-container,
78 | .pl-branding {
79 | display: none;
80 | }
81 | .mobile-nav {
82 | display: flex;
83 | width: 100%;
84 | }
85 | .nav-component {
86 | margin: 2vh;
87 | font-size: 1.25rem;
88 | }
89 | .m-github {
90 | margin-left: auto;
91 | }
92 | }
93 | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 1) {
94 | padding: 2vh;
95 | grid-template-columns: 1fr;
96 | .nav-components-container,
97 | .pl-branding {
98 | display: none;
99 | }
100 | .mobile-nav {
101 | display: flex;
102 | width: 100%;
103 | }
104 | .nav-component {
105 | margin: 2vh;
106 | font-size: 2.5rem;
107 | }
108 | .m-github {
109 | margin-left: auto;
110 | }
111 | }
112 | `;
113 |
114 | class Navbar extends Component {
115 | componentDidMount() {
116 | this.props.getGitHubStars();
117 | }
118 |
119 | render() {
120 | const { stars } = this.props;
121 | return (
122 |
123 |
124 | {/*
*/}
125 |
ProjectLearn.io
126 |
127 |
150 |
174 |
175 | );
176 | }
177 | }
178 |
179 | const mapStateToProps = (state) => {
180 | return {
181 | stars: state.data.stars,
182 | };
183 | };
184 |
185 | const mapDispatchToProps = {
186 | getGitHubStars,
187 | };
188 |
189 | export default connect(mapStateToProps, mapDispatchToProps)(Navbar);
190 |
--------------------------------------------------------------------------------
/src/components/landing/Newsletter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components";
3 |
4 | import axios from "axios";
5 |
6 | const NewsWrapper = styled.div`
7 | display: flex;
8 | flex-direction: column;
9 | align-items: center;
10 | margin-bottom: 10vh;
11 |
12 | h1 {
13 | font-weight: normal;
14 | margin: 0vh;
15 | font-size: 4vh;
16 | }
17 | h2 {
18 | font-weight: normal;
19 | color: var(--themeTextSecondaryDark);
20 | font-size: 3vh;
21 | margin: 0vh;
22 | margin-bottom: 2vh;
23 | margin-top: 0.5vh;
24 | }
25 | .form {
26 | display: flex;
27 | flex-direction: row;
28 | align-items: center;
29 | }
30 | .input-field {
31 | width: 20vw;
32 | outline: none;
33 | box-shadow: 0 1px 7px rgba(0, 0, 0, 0.05);
34 | border: 1px solid rgba(0, 0, 0, 0.04) !important;
35 | padding: 1.5vh 2vh;
36 | border-radius: 1vh 0 0 1vh;
37 | background: linear-gradient(135deg,rgba(255,255,255,0.05) 10.93%,rgba(255,255,255,0) 90%);
38 | color: white;
39 | border-radius: 1.5vh;
40 | font-size: 2.8vh;
41 | }
42 | .on-error {
43 | margin-top: 1vh;
44 | font-size: 2.5vh;
45 | color: var(--theme-red);
46 | }
47 | .on-success {
48 | margin-top: 1vh;
49 | font-size: 2.5vh;
50 | color: var(--theme-green);
51 | }
52 | .submit-btn {
53 | width: fit-content;
54 | margin-left: -1vw;
55 | outline: none;
56 | box-shadow: 0 1px 7px rgba(0, 0, 0, 0.05);
57 | border: 1px solid rgba(0, 0, 0, 0.04) !important;
58 | padding: 1.5vh 2vh;
59 | border-radius: 0 1.5vh 1.5vh 0;
60 | font-size: 2.8vh;
61 | background: var(--button-blue);
62 | cursor: pointer;
63 | color: white;
64 | }
65 |
66 | @media only screen and (min-width: 320px) and (max-width: 480px) {
67 | margin-bottom: 5vh;
68 | .form {
69 | display: flex;
70 | flex-direction: column;
71 | align-items: center;
72 | }
73 | .input-field {
74 | width: 60vw;
75 | padding: 2vh;
76 | border-radius: 1vh;
77 | font-size: 2.5vh;
78 | }
79 | .on-error,
80 | .on-success {
81 | font-size: 2vh;
82 | }
83 | .submit-btn {
84 | margin-top: 2vh;
85 | width: 68vw;
86 | margin-left: unset;
87 | padding: 2vh;
88 | border-radius: 1vh;
89 | font-size: 2.5vh;
90 | color: white;
91 | }
92 | h2 {
93 | font-size: 2.5vh;
94 | }
95 | }
96 | `;
97 |
98 | export default function Newsletter() {
99 | const [email, setEmail] = useState("");
100 | const [componentState, setcomponentState] = useState("");
101 | const [loading, setLoading] = useState(false);
102 |
103 | const handleSubmit = (evt) => {
104 | evt.preventDefault();
105 | setLoading(true);
106 | const options = {
107 | headers: {
108 | accept: "application/json",
109 | "content-type": "application/json",
110 | "api-key": process.env.EMAIL_API,
111 | },
112 | };
113 |
114 | axios
115 | .post(
116 | "https://api.sendinblue.com/v3/contacts",
117 | `{"updateEnabled":false,"email":"${email}"}`,
118 | options
119 | )
120 | .then((response) => {
121 | setcomponentState("success");
122 | setLoading(false);
123 | })
124 | .catch((error) => {
125 | setcomponentState("error");
126 | setLoading(false);
127 | });
128 | };
129 |
130 | return (
131 |
132 | {/* Stay in the Loop! */}
133 | Get notified about new updates!
134 |
149 | {componentState === "error" ? (
150 | Subscription failed! Please try again later.
151 | ) : componentState === "success" ? (
152 | Thank you for signing up!
153 | ) : null}
154 |
155 | );
156 | }
157 |
--------------------------------------------------------------------------------
/src/components/landing/Splash.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Link from "next/link";
3 | import styled from "styled-components";
4 | import plSplash from "../../images/pl-splash.svg";
5 | import GithubLoginButton from "../GitHubLogin.jsx";
6 |
7 | import { analytics } from "@/lib/analytics";
8 |
9 | export default function Splash() {
10 | // Tracking function for the contribute button
11 | const trackContribute = () => {
12 | analytics.track("Contribute Button Clicked", {
13 | source: "splash page",
14 | });
15 | };
16 |
17 | return (
18 |
19 |
20 |
Learn By Doing
21 |
22 | Tutorials are great, but building projects is the best way to learn.
23 | Do project based learning and
24 | learn code the right way!
25 |
26 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 |
46 | const SplashWrapper = styled.div`
47 | margin: 0 5vw;
48 | border-radius: 2vw;
49 | background: var(--themeDark);
50 | background: linear-gradient(
51 | 135deg,
52 | rgba(255, 255, 255, 0.05) 10.93%,
53 | rgba(255, 255, 255, 0) 90%
54 | );
55 | color: white;
56 | border-radius: 15px;
57 | display: grid;
58 | grid-template-rows: 1fr;
59 | grid-template-columns: auto auto;
60 | .pl-details {
61 | display: flex;
62 | flex-direction: column;
63 | justify-content: center;
64 | text-align: center;
65 | padding: 3rem 0 3rem 3rem;
66 | }
67 | .title {
68 | font-size: 8vh;
69 | margin: 0 0 3vh 0;
70 | }
71 | .sub-title {
72 | font-size: 3vh;
73 | margin: 0 0vh 5vh 0;
74 | color: var(--themeTextSecondaryDark);
75 | }
76 | .learn-more {
77 | width: fit-content;
78 | padding: 1.5vh 2vh;
79 | font-size: 2.8vh;
80 | background: var(--button-blue);
81 | color: white;
82 | border: none;
83 | border-radius: 1.5vh;
84 | outline: none;
85 | cursor: pointer;
86 | }
87 | .contribute-btn {
88 | width: fit-content;
89 | padding: 1.5vh 2vh;
90 | font-size: 2.8vh;
91 | background: rgba(255, 255, 255, 0.05);
92 | color: white;
93 | border: none;
94 | border-radius: 1.5vh;
95 | outline: none;
96 | cursor: pointer;
97 | margin-left: 1.5rem;
98 | }
99 | .splash-image-container {
100 | margin-left: auto;
101 | padding: 2rem;
102 | display: flex;
103 | align-items: center;
104 | }
105 | .splash-image {
106 | width: 40vw;
107 | }
108 | .rainbow-text {
109 | background: linear-gradient(90deg, #4ca5ff 2.34%, #b673f8 100.78%);
110 | -webkit-background-clip: text;
111 | -webkit-text-fill-color: transparent;
112 | text-fill-color: transparent;
113 | background-clip: text;
114 | font-weight: bold;
115 | }
116 | @media only screen and (max-width: 767px) {
117 | grid-template-columns: 1fr;
118 | .pl-details {
119 | padding: 2rem;
120 | }
121 | .title {
122 | font-size: 10vw;
123 | margin: 0 0 3vh 0;
124 | }
125 | .sub-title {
126 | font-size: 5vw;
127 | margin: 0 0 4vh 0;
128 | }
129 | .splash-image-container {
130 | margin-left: unset;
131 | padding: 0rem;
132 | margin-right: 0rem;
133 | display: flex;
134 | align-items: center;
135 | }
136 | .splash-image {
137 | width: 100%;
138 | }
139 | .learn-more {
140 | font-size: 4.5vw;
141 | }
142 | .contribute-btn {
143 | display: none;
144 | }
145 | }
146 | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 1) {
147 | grid-template-columns: 1fr;
148 | .pl-details {
149 | padding: 2rem;
150 | text-align: center;
151 | justify-content: center;
152 | }
153 | .title {
154 | font-size: 8vw;
155 | margin: 0 0 3vh 0;
156 | }
157 | .sub-title {
158 | font-size: 4.5vw;
159 | margin: 0 0 4vh 0;
160 | }
161 | .splash-image-container {
162 | margin-left: unset;
163 | padding: 0rem;
164 | margin-right: 0rem;
165 | display: flex;
166 | align-items: center;
167 | }
168 | .splash-image {
169 | width: 100%;
170 | }
171 | .learn-more {
172 | font-size: 4.7vw;
173 | margin: 0 auto;
174 | }
175 | }
176 | `;
177 |
--------------------------------------------------------------------------------
/src/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { ThemeProvider as NextThemesProvider } from "next-themes"
5 | import { type ThemeProviderProps } from "next-themes/dist/types"
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children}
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
3 | import { ChevronDown } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Accordion = AccordionPrimitive.Root
8 |
9 | const AccordionItem = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
18 | ))
19 | AccordionItem.displayName = "AccordionItem"
20 |
21 | const AccordionTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, children, ...props }, ref) => (
25 |
26 | svg]:rotate-180",
30 | className
31 | )}
32 | {...props}
33 | >
34 | {children}
35 |
36 |
37 |
38 | ))
39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
40 |
41 | const AccordionContent = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef
44 | >(({ className, children, ...props }, ref) => (
45 |
50 | {children}
51 |
52 | ))
53 |
54 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
55 |
56 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
57 |
--------------------------------------------------------------------------------
/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Avatar = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 | ))
19 | Avatar.displayName = AvatarPrimitive.Root.displayName
20 |
21 | const AvatarImage = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
30 | ))
31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
32 |
33 | const AvatarFallback = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, ...props }, ref) => (
37 |
45 | ))
46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47 |
48 | export { Avatar, AvatarImage, AvatarFallback }
49 |
--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/src/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { ChevronLeft, ChevronRight } from "lucide-react"
3 | import { DayPicker } from "react-day-picker"
4 |
5 | import { cn } from "@/lib/utils"
6 | import { buttonVariants } from "@/components/ui/button"
7 |
8 | export type CalendarProps = React.ComponentProps
9 |
10 | function Calendar({
11 | className,
12 | classNames,
13 | showOutsideDays = true,
14 | ...props
15 | }: CalendarProps) {
16 | return (
17 | ,
56 | IconRight: ({ ...props }) => ,
57 | }}
58 | {...props}
59 | />
60 | )
61 | }
62 | Calendar.displayName = "Calendar"
63 |
64 | export { Calendar }
65 |
--------------------------------------------------------------------------------
/src/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { type DialogProps } from "@radix-ui/react-dialog"
3 | import { Command as CommandPrimitive } from "cmdk"
4 | import { Search } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 | import { Dialog, DialogContent } from "@/components/ui/dialog"
8 |
9 | const Command = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 | ))
22 | Command.displayName = CommandPrimitive.displayName
23 |
24 | interface CommandDialogProps extends DialogProps {}
25 |
26 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
27 | return (
28 |
29 |
30 |
31 | {children}
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | const CommandInput = React.forwardRef<
39 | React.ElementRef,
40 | React.ComponentPropsWithoutRef
41 | >(({ className, ...props }, ref) => (
42 |
43 |
44 |
52 |
53 | ))
54 |
55 | CommandInput.displayName = CommandPrimitive.Input.displayName
56 |
57 | const CommandList = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
66 | ))
67 |
68 | CommandList.displayName = CommandPrimitive.List.displayName
69 |
70 | const CommandEmpty = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >((props, ref) => (
74 |
79 | ))
80 |
81 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
82 |
83 | const CommandGroup = React.forwardRef<
84 | React.ElementRef,
85 | React.ComponentPropsWithoutRef
86 | >(({ className, ...props }, ref) => (
87 |
95 | ))
96 |
97 | CommandGroup.displayName = CommandPrimitive.Group.displayName
98 |
99 | const CommandSeparator = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
110 |
111 | const CommandItem = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
123 | ))
124 |
125 | CommandItem.displayName = CommandPrimitive.Item.displayName
126 |
127 | const CommandShortcut = ({
128 | className,
129 | ...props
130 | }: React.HTMLAttributes) => {
131 | return (
132 |
139 | )
140 | }
141 | CommandShortcut.displayName = "CommandShortcut"
142 |
143 | export {
144 | Command,
145 | CommandDialog,
146 | CommandInput,
147 | CommandList,
148 | CommandEmpty,
149 | CommandGroup,
150 | CommandItem,
151 | CommandShortcut,
152 | CommandSeparator,
153 | }
154 |
--------------------------------------------------------------------------------
/src/components/ui/date-picker.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { format } from "date-fns"
3 | import { Calendar as CalendarIcon } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 | import { Button } from "@/components/ui/button"
7 | import { Calendar } from "@/components/ui/calendar"
8 | import {
9 | Popover,
10 | PopoverContent,
11 | PopoverTrigger,
12 | } from "@/components/ui/popover"
13 |
14 | // Accept props for selected date and onSelect callback
15 | export function DatePicker({
16 | selected,
17 | onSelect
18 | }: {
19 | selected: Date | undefined
20 | onSelect: (date: Date | undefined) => void
21 | }) {
22 | const [date, setDate] = React.useState(selected)
23 |
24 | const handleDateChange = (newDate: Date | undefined) => {
25 | setDate(newDate);
26 | onSelect(newDate); // Pass the selected date to the parent component
27 | };
28 |
29 | return (
30 |
31 |
32 |
39 |
40 | {date ? format(date, "PPP") : Pick a date }
41 |
42 |
43 |
44 |
50 |
51 |
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as DialogPrimitive from "@radix-ui/react-dialog"
3 | import { X } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Dialog = DialogPrimitive.Root
8 |
9 | const DialogTrigger = DialogPrimitive.Trigger
10 |
11 | const DialogPortal = DialogPrimitive.Portal
12 |
13 | const DialogClose = DialogPrimitive.Close
14 |
15 | const DialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
29 |
30 | const DialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, children, ...props }, ref) => (
34 |
35 |
36 |
44 | {children}
45 |
46 |
47 | Close
48 |
49 |
50 |
51 | ))
52 | DialogContent.displayName = DialogPrimitive.Content.displayName
53 |
54 | const DialogHeader = ({
55 | className,
56 | ...props
57 | }: React.HTMLAttributes) => (
58 |
65 | )
66 | DialogHeader.displayName = "DialogHeader"
67 |
68 | const DialogFooter = ({
69 | className,
70 | ...props
71 | }: React.HTMLAttributes) => (
72 |
79 | )
80 | DialogFooter.displayName = "DialogFooter"
81 |
82 | const DialogTitle = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
94 | ))
95 | DialogTitle.displayName = DialogPrimitive.Title.displayName
96 |
97 | const DialogDescription = React.forwardRef<
98 | React.ElementRef,
99 | React.ComponentPropsWithoutRef
100 | >(({ className, ...props }, ref) => (
101 |
106 | ))
107 | DialogDescription.displayName = DialogPrimitive.Description.displayName
108 |
109 | export {
110 | Dialog,
111 | DialogPortal,
112 | DialogOverlay,
113 | DialogClose,
114 | DialogTrigger,
115 | DialogContent,
116 | DialogHeader,
117 | DialogFooter,
118 | DialogTitle,
119 | DialogDescription,
120 | }
121 |
--------------------------------------------------------------------------------
/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from "react-hook-form"
12 |
13 | import { cn } from "@/lib/utils"
14 | import { Label } from "@/components/ui/label"
15 |
16 | const Form = FormProvider
17 |
18 | type FormFieldContextValue<
19 | TFieldValues extends FieldValues = FieldValues,
20 | TName extends FieldPath = FieldPath
21 | > = {
22 | name: TName
23 | }
24 |
25 | const FormFieldContext = React.createContext(
26 | {} as FormFieldContextValue
27 | )
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext)
44 | const itemContext = React.useContext(FormItemContext)
45 | const { getFieldState, formState } = useFormContext()
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState)
48 |
49 | if (!fieldContext) {
50 | throw new Error("useFormField should be used within ")
51 | }
52 |
53 | const { id } = itemContext
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | }
63 | }
64 |
65 | type FormItemContextValue = {
66 | id: string
67 | }
68 |
69 | const FormItemContext = React.createContext(
70 | {} as FormItemContextValue
71 | )
72 |
73 | const FormItem = React.forwardRef<
74 | HTMLDivElement,
75 | React.HTMLAttributes
76 | >(({ className, ...props }, ref) => {
77 | const id = React.useId()
78 |
79 | return (
80 |
81 |
82 |
83 | )
84 | })
85 | FormItem.displayName = "FormItem"
86 |
87 | const FormLabel = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => {
91 | const { error, formItemId } = useFormField()
92 |
93 | return (
94 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const labelVariants = cva(
8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9 | )
10 |
11 | const Label = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef &
14 | VariantProps
15 | >(({ className, ...props }, ref) => (
16 |
21 | ))
22 | Label.displayName = LabelPrimitive.Root.displayName
23 |
24 | export { Label }
25 |
--------------------------------------------------------------------------------
/src/components/ui/loading-spinner.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from "styled-components";
3 |
4 | const Loader = styled.div`
5 | .loader {
6 | border: 3px solid black;
7 | border-radius: 50%;
8 | border-top: 3px solid #f3f3f3;
9 | width: 15px;
10 | height: 15px;
11 | margin-left: 10px;
12 | margin-right: 10px;
13 | -webkit-animation: spin 2s linear infinite; /* Safari */
14 | animation: spin 2s linear infinite;
15 | }
16 |
17 | /* Safari */
18 | @-webkit-keyframes spin {
19 | 0% { -webkit-transform: rotate(0deg); }
20 | 100% { -webkit-transform: rotate(360deg); }
21 | }
22 |
23 | @keyframes spin {
24 | 0% { transform: rotate(0deg); }
25 | 100% { transform: rotate(360deg); }
26 | }
27 | }`
28 |
29 | export default function Circle() {
30 | return (
31 |
32 |
33 |
34 | )
35 | }
--------------------------------------------------------------------------------
/src/components/ui/multi-select.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { X } from "lucide-react";
5 | import { Badge } from "@/components/ui/badge";
6 | import {
7 | Command,
8 | CommandGroup,
9 | CommandItem,
10 | CommandList,
11 | } from "@/components/ui/command";
12 | import { Command as CommandPrimitive } from "cmdk";
13 |
14 | type TechOption = Record<"value" | "label", string>;
15 |
16 | interface FancyMultiSelectProps {
17 | options: TechOption[];
18 | selectedOptions: TechOption[];
19 | onSelect: (selected: TechOption[]) => void;
20 | }
21 |
22 | export function MultiSelect({ options, selectedOptions, onSelect }: FancyMultiSelectProps) {
23 | const inputRef = React.useRef(null);
24 | const [open, setOpen] = React.useState(false);
25 | const [inputValue, setInputValue] = React.useState("");
26 |
27 | const handleSelect = (tech: TechOption) => {
28 | onSelect([...selectedOptions, tech]); // Update selected options
29 | };
30 |
31 | const handleUnselect = React.useCallback((tech: TechOption) => {
32 | onSelect(selectedOptions.filter((s) => s.value !== tech.value));
33 | }, [selectedOptions, onSelect]);
34 |
35 | const handleKeyDown = React.useCallback((e: React.KeyboardEvent) => {
36 | const input = inputRef.current;
37 |
38 | if (e.key === "Enter") {
39 | e.preventDefault(); // Prevent form submission if inside a form
40 | const newTech: TechOption = { value: inputValue.trim(), label: inputValue.trim() };
41 |
42 | if (inputValue.trim() && !selectedOptions.some(selected => selected.value === newTech.value)) {
43 | handleSelect(newTech); // Add new custom entry
44 | setInputValue(""); // Clear input after selection
45 | }
46 | }
47 |
48 | if (input) {
49 | if (e.key === "Delete" || e.key === "Backspace") {
50 | if (input.value === "") {
51 | handleUnselect(selectedOptions[selectedOptions.length - 1]); // Remove last selected item
52 | }
53 | }
54 | if (e.key === "Escape") {
55 | input.blur();
56 | }
57 | }
58 | }, [inputValue, selectedOptions]);
59 |
60 | // Handle adding new tech on blur
61 | const handleBlur = () => {
62 | const newTech: TechOption = { value: inputValue.trim(), label: inputValue.trim() };
63 |
64 | if (inputValue.trim() && !selectedOptions.some(selected => selected.value === newTech.value)) {
65 | handleSelect(newTech); // Add new custom entry
66 | setInputValue(""); // Clear input after selection
67 | }
68 |
69 | setOpen(false); // Close the options list on blur
70 | };
71 |
72 | const selectables = options.filter((option) =>
73 | !selectedOptions.some(selected => selected.value === option.value)
74 | );
75 |
76 | return (
77 |
78 |
79 |
80 | {selectedOptions.map((tech) => (
81 |
82 | {tech.label}
83 | {
86 | if (e.key === "Enter") {
87 | handleUnselect(tech);
88 | }
89 | }}
90 | onMouseDown={(e) => {
91 | e.preventDefault();
92 | e.stopPropagation();
93 | }}
94 | onClick={() => handleUnselect(tech)}
95 | >
96 |
97 |
98 |
99 | ))}
100 | setOpen(true)}
106 | placeholder="Select tech stack or add custom..."
107 | className="ml-2 flex-1 max-w-[250px] bg-transparent outline-none placeholder:text-muted-foreground"
108 | />
109 |
110 |
111 |
112 |
113 | {open && selectables.length > 0 ? (
114 |
115 |
116 | {selectables.map((tech) => (
117 | {
120 | e.preventDefault();
121 | e.stopPropagation();
122 | }}
123 | onSelect={() => handleSelect(tech)}
124 | className="cursor-pointer hover:bg-gray-200"
125 | >
126 | {tech.label}
127 |
128 | ))}
129 |
130 |
131 | ) : null}
132 |
133 |
134 |
135 | );
136 | }
137 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as PopoverPrimitive from "@radix-ui/react-popover"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Popover = PopoverPrimitive.Root
7 |
8 | const PopoverTrigger = PopoverPrimitive.Trigger
9 |
10 | const PopoverContent = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14 |
15 |
25 |
26 | ))
27 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
28 |
29 | export { Popover, PopoverTrigger, PopoverContent }
30 |
--------------------------------------------------------------------------------
/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SelectPrimitive from "@radix-ui/react-select"
3 | import { Check, ChevronDown, ChevronUp } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Select = SelectPrimitive.Root
8 |
9 | const SelectGroup = SelectPrimitive.Group
10 |
11 | const SelectValue = SelectPrimitive.Value
12 |
13 | const SelectTrigger = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef
16 | >(({ className, children, ...props }, ref) => (
17 | span]:line-clamp-1",
21 | className
22 | )}
23 | {...props}
24 | >
25 | {children}
26 |
27 |
28 |
29 |
30 | ))
31 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
32 |
33 | const SelectScrollUpButton = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, ...props }, ref) => (
37 |
45 |
46 |
47 | ))
48 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
49 |
50 | const SelectScrollDownButton = React.forwardRef<
51 | React.ElementRef,
52 | React.ComponentPropsWithoutRef
53 | >(({ className, ...props }, ref) => (
54 |
62 |
63 |
64 | ))
65 | SelectScrollDownButton.displayName =
66 | SelectPrimitive.ScrollDownButton.displayName
67 |
68 | const SelectContent = React.forwardRef<
69 | React.ElementRef,
70 | React.ComponentPropsWithoutRef
71 | >(({ className, children, position = "popper", ...props }, ref) => (
72 |
73 |
84 |
85 |
92 | {children}
93 |
94 |
95 |
96 |
97 | ))
98 | SelectContent.displayName = SelectPrimitive.Content.displayName
99 |
100 | const SelectLabel = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
109 | ))
110 | SelectLabel.displayName = SelectPrimitive.Label.displayName
111 |
112 | const SelectItem = React.forwardRef<
113 | React.ElementRef,
114 | React.ComponentPropsWithoutRef
115 | >(({ className, children, ...props }, ref) => (
116 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | {children}
131 |
132 | ))
133 | SelectItem.displayName = SelectPrimitive.Item.displayName
134 |
135 | const SelectSeparator = React.forwardRef<
136 | React.ElementRef,
137 | React.ComponentPropsWithoutRef
138 | >(({ className, ...props }, ref) => (
139 |
144 | ))
145 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
146 |
147 | export {
148 | Select,
149 | SelectGroup,
150 | SelectValue,
151 | SelectTrigger,
152 | SelectContent,
153 | SelectLabel,
154 | SelectItem,
155 | SelectSeparator,
156 | SelectScrollUpButton,
157 | SelectScrollDownButton,
158 | }
159 |
--------------------------------------------------------------------------------
/src/images/challenge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/challenge.png
--------------------------------------------------------------------------------
/src/images/coding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/coding.png
--------------------------------------------------------------------------------
/src/images/game-dev-cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/game-dev-cat.png
--------------------------------------------------------------------------------
/src/images/game-dev-cat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/game-dev-cat.webp
--------------------------------------------------------------------------------
/src/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/logo.png
--------------------------------------------------------------------------------
/src/images/ml-cat-alt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/ml-cat-alt.png
--------------------------------------------------------------------------------
/src/images/ml-cat-alt.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/ml-cat-alt.webp
--------------------------------------------------------------------------------
/src/images/ml-cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/ml-cat.png
--------------------------------------------------------------------------------
/src/images/mob-dev-cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/mob-dev-cat.png
--------------------------------------------------------------------------------
/src/images/pl-splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/pl-splash.png
--------------------------------------------------------------------------------
/src/images/pl-splash.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/pl-splash.webp
--------------------------------------------------------------------------------
/src/images/powered-by-namecheap-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/powered-by-namecheap-black.png
--------------------------------------------------------------------------------
/src/images/powered-by-namecheap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/powered-by-namecheap.png
--------------------------------------------------------------------------------
/src/images/prog-lang-cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/prog-lang-cat.png
--------------------------------------------------------------------------------
/src/images/prog-lang-cat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/prog-lang-cat.webp
--------------------------------------------------------------------------------
/src/images/rocket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/rocket.png
--------------------------------------------------------------------------------
/src/images/web-dev-cat-alt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/web-dev-cat-alt.png
--------------------------------------------------------------------------------
/src/images/web-dev-cat-alt.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/web-dev-cat-alt.webp
--------------------------------------------------------------------------------
/src/images/web-dev-cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xtremilicious/projectlearn-project-based-learning/ed82c9ce8c2d741b4e04f58c6c8d5b713efed656/src/images/web-dev-cat.png
--------------------------------------------------------------------------------
/src/lib/analytics.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { init, logEvent, setUserId } from '@amplitude/analytics-browser';
3 |
4 | // Your analytics object can still be defined here
5 | export const analytics = {
6 | init: () => {
7 | init(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY, {
8 | defaultTracking: false
9 | });
10 | },
11 | track: (eventName, eventProperties) => {
12 | logEvent(eventName, eventProperties);
13 |
14 | // Log event tracking in development mode
15 | if (process.env.NODE_ENV === 'development') {
16 | console.log(`Tracked Event: ${eventName}`, eventProperties);
17 | }
18 | },
19 | setUserId: (userId) => {
20 | setUserId(userId);
21 |
22 | // Log user ID setting in development mode
23 | if (process.env.NODE_ENV === 'development') {
24 | console.log(`Set User ID: ${userId}`);
25 | }
26 | },
27 | };
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/src/redux/actions/dataActions.ts:
--------------------------------------------------------------------------------
1 | import { GET_GITHUB_STARS, GET_PROJECTS } from "../types";
2 | import axios from "axios";
3 | import { fetchProjects } from "../../utils/data"; // Import the fetchProjects function
4 |
5 | export const getGitHubStars = () => dispatch => {
6 | axios
7 | .get(
8 | `https://api.github.com/repos/Xtremilicious/ProjectLearn-Project-Based-Learning`
9 | )
10 | .then(res => {
11 | dispatch({
12 | type: GET_GITHUB_STARS,
13 | payload: res.data.stargazers_count
14 | });
15 | })
16 | .catch(error => {
17 | console.error("Error fetching GitHub stars:", error);
18 | // Handle error if necessary
19 | });
20 | };
21 |
22 | export const getProjects = () => async dispatch => {
23 | try {
24 | const projectsData = await fetchProjects(); // Fetch projects data
25 | dispatch({
26 | type: GET_PROJECTS,
27 | payload: projectsData // Use fetched data
28 | });
29 | } catch (error) {
30 | console.error("Error fetching projects:", error);
31 | // Handle error if necessary
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/src/redux/reducers/dataReducer.ts:
--------------------------------------------------------------------------------
1 | import { GET_GITHUB_STARS, GET_PROJECTS } from "../types";
2 |
3 | const initialState = {
4 | stars: null,
5 | projects: null
6 | };
7 |
8 | export default function(state = initialState, action) {
9 | switch (action.type) {
10 | case GET_GITHUB_STARS:
11 | return {
12 | ...state,
13 | stars: action.payload
14 | };
15 | case GET_PROJECTS:
16 | return {
17 | ...state,
18 | projects: action.payload
19 |
20 | };
21 |
22 | default:
23 | return state;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/redux/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore, combineReducers } from '@reduxjs/toolkit';
2 | import thunk from 'redux-thunk';
3 | import dataReducer from './reducers/dataReducer';
4 |
5 | const initialState = {};
6 |
7 | const rootReducer = combineReducers({
8 | data: dataReducer
9 | });
10 |
11 | const store = configureStore({
12 | reducer: rootReducer,
13 | middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(thunk),
14 | devTools: process.env.NODE_ENV !== 'production',
15 | preloadedState: initialState
16 | });
17 |
18 | export default store;
19 |
--------------------------------------------------------------------------------
/src/redux/types.ts:
--------------------------------------------------------------------------------
1 | export const GET_GITHUB_STARS = 'GET_GITHUB_STARS';
2 | export const GET_PROJECTS = 'GET_PROJECTS';
--------------------------------------------------------------------------------
/src/utils/data.ts:
--------------------------------------------------------------------------------
1 | // utils/data.js
2 |
3 | export const fetchProjects = async () => {
4 | const response = await fetch('/data.json'); // Assuming data.json is in the root directory
5 | if (!response.ok) {
6 | throw new Error('Failed to fetch projects');
7 | }
8 | const projectsData = await response.json();
9 | return projectsData;
10 | };
11 |
--------------------------------------------------------------------------------
/src/utils/functions.ts:
--------------------------------------------------------------------------------
1 | //Shuffle Projects
2 | export const shuffle = array => {
3 | var currentIndex = array.length,
4 | temporaryValue,
5 | randomIndex;
6 |
7 | while (0 !== currentIndex) {
8 |
9 | randomIndex = Math.floor(Math.random() * currentIndex);
10 | currentIndex -= 1;
11 |
12 | temporaryValue = array[currentIndex];
13 | array[currentIndex] = array[randomIndex];
14 | array[randomIndex] = temporaryValue;
15 | }
16 |
17 | return array;
18 | };
19 |
20 | export const getRandomInt = (max) => {
21 | return Math.floor(Math.random() * Math.floor(max));
22 | }
23 |
24 | export const youtubeGetID = (url) => {
25 | var ID = "";
26 | url = url.replace(/(>|<)/gi, "").split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
27 | if (url[2] !== undefined) {
28 | ID = url[2].split(/[^0-9a-z_\-]/i);
29 | ID = ID[0];
30 | } else {
31 | ID = url;
32 | }
33 | return ID;
34 | }
--------------------------------------------------------------------------------
/src/utils/generate_readme.ts:
--------------------------------------------------------------------------------
1 | let fs = require("fs");
2 | let path = require("path");
3 |
4 | function generateMD() {
5 | let fileName = "README";
6 | let fileContents = `
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
23 |
24 | Tutorials are great, but building projects is the best way to learn. Do project based learning and learn code the right way!
25 |
26 | ProjectLearn provides a curated list of project tutorials in which learners build an application from scratch. These are divided into different categories, namely, web development, mobile development, game development, machine learning, deep learning and artificial intelligence.
27 |
28 | The list has project tutorials on many in-demand languages and technologies including ReactJS, NodeJS, VueJS, Flutter, React Native, .NET Core, Unity, TensorFlow, OpenCV, Keras, and more.
29 |
30 | To contribute to this list, head over to [CONTRIBUTE.md](https://github.com/Xtremilicious/ProjectLearn-Project-Based-Learning/blob/master/CONTRIBUTE.md) for more details :)
31 |
32 | ## List of Project Tutorials:
33 | `;
34 |
35 | const projectsData = require("../../public/data.json");
36 | const domains = [
37 | ["web-dev", "web-development", "Web Development"],
38 | ["mob-dev", "mobile-development", "Mobile Development"],
39 | ["game-dev", "game-development", "Game Development"],
40 | ["ml-ai", "machine-learning-and-ai", "Machine Learning & AI"],
41 | ];
42 |
43 | for (domain of domains) {
44 | fileContents = fileContents.concat(
45 | `### ${domain[2]}: \n| Project | Technologies | Link |\n| :--- |:---|:---|\n`
46 | );
47 |
48 | for (let i = 0; i < projectsData.length; i++) {
49 | slug = domain[1];
50 | t = domain[0];
51 |
52 | if (projectsData[i].category.includes(t)) {
53 | fileContents = fileContents.concat(
54 | `| ${projectsData[i].title} | ${projectsData[i].tech
55 | .slice(0, 5)
56 | .join(", ")} | [Link](https://projectlearn.io/learn/${slug}/project/${projectsData[i].title
57 | .toLowerCase()
58 | .split(" ")
59 | .join("-")}-${projectsData[i].id}?from=github)|`.concat("\n")
60 | );
61 | }
62 | }
63 | }
64 |
65 | fileContents = fileContents.concat(`
66 |
67 |
`);
68 |
69 | let outputPath = path.join(__dirname, "../../", `${fileName}.md`);
70 |
71 | fs.writeFile(outputPath, fileContents, function (err) {
72 | if (err) {
73 | return console.log(err);
74 | }
75 | console.log(outputPath + " file generated");
76 | });
77 | }
78 |
79 | generateMD();
80 |
--------------------------------------------------------------------------------
/src/utils/generate_sitemap.ts:
--------------------------------------------------------------------------------
1 | const sitemap = require("nextjs-sitemap-generator");
2 | const path = require("path");
3 |
4 | sitemap({
5 | baseUrl: "https://projectlearn.io",
6 | ignoredPaths: ["admin"],
7 | pagesDirectory: path.join(__dirname, "../../pages"),
8 | targetDirectory: "out/",
9 | nextConfigPath: path.join(__dirname, "../../next.config.js"),
10 | ignoredExtensions: ["png", "jpg"],
11 | pagesConfig: {
12 | "/login": {
13 | priority: "0.5",
14 | changefreq: "daily"
15 | }
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/src/utils/styles.ts:
--------------------------------------------------------------------------------
1 | import css from "styled-jsx/css";
2 |
3 |
4 | export default css.global`
5 | :root {
6 | --button-blue: #6c63ff;
7 | --theme-pink: #f9d9eb;
8 | --theme-pink-alt: #d4919a;
9 | --theme-blue: #aac6fc;
10 | --theme-blue-alt: #719FF0;
11 | --theme-green: #7fd6c2;
12 | --theme-green-alt: #04b486;
13 | --theme-yellow: #f6e049;
14 | --theme-yellow-alt: #dba901;
15 | --theme-red: #ff8578;
16 | --theme-red-alt: #e6a29a;
17 | --theme-grey: #586069;
18 | --dashboard-purple: #8d8eee;
19 | --dashboard-purple-alt: #6b6be5;
20 | --mainDark: #2E2E2E;
21 | --secondaryDark: #424242;
22 | --themeDark: #0d1117;
23 | --themeTextSecondaryDark: rgb(163 179 188);
24 | }
25 |
26 | @font-face {
27 | font-family: "Lato";
28 | src: url("/fonts/Lato-Regular.ttf");
29 | font-style: normal;
30 | font-display: swap;
31 | }
32 |
33 | @font-face {
34 | font-family: "LatoBold";
35 | src: url("/fonts/Lato-Bold.ttf");
36 | font-style: normal;
37 | font-display: swap;
38 | }
39 |
40 |
41 | html {
42 | scroll-behavior: smooth;
43 | }
44 | body {
45 | font-family: "Lato", "sans-serif";
46 | margin: 0;
47 | padding: 0;
48 | }
49 | button{
50 | font-family: "Lato", "sans-serif";
51 | }
52 | a {
53 | text-decoration: none;
54 | color: initial;
55 | }
56 | `;
57 |
--------------------------------------------------------------------------------
/src/utils/technologies.ts:
--------------------------------------------------------------------------------
1 | export const webDev = [
2 | {
3 | category: "HTML5",
4 | icon: "html5"
5 | },
6 | {
7 | category: "CSS3",
8 | icon: "css3"
9 | },
10 | {
11 | category: "JavaScript",
12 | icon: "js"
13 | },
14 | {
15 | category: "Angular",
16 | icon: "angular"
17 | },
18 | {
19 | category: "React",
20 | icon: "react"
21 | },
22 | {
23 | category: "Vue",
24 | icon: "vuejs"
25 | },
26 | {
27 | category: "Node",
28 | icon: "node-js"
29 | }
30 | ];
31 |
32 | export const mobDev = [
33 | {
34 | category: "Android",
35 | icon: "android"
36 | },
37 | {
38 | category: "iOS",
39 | icon: "apple"
40 | },
41 | {
42 | category: "Java",
43 | icon: "java"
44 | },
45 | {
46 | category: "Swift",
47 | icon: "swift"
48 | },
49 | {
50 | category: "Flutter",
51 | },
52 | {
53 | category: "React Native",
54 | icon: "react"
55 | }
56 | ];
57 |
58 | export const gameDev = [
59 | {
60 | category: "C/C++",
61 | icon: "cuttlefish"
62 | },
63 | {
64 | category: "C#",
65 | icon: "microsoft"
66 | },
67 | {
68 | category: "Python",
69 | icon: "python"
70 | },
71 | {
72 | category: ".NET",
73 | icon: "microsoft"
74 | },
75 | {
76 | category: "JavaScript",
77 | icon: "js"
78 | },
79 | {
80 | category: "Unity",
81 | icon: "unity"
82 | }
83 | ];
84 |
85 | export const mlAI = [
86 | {
87 | category: "Python",
88 | icon: "python"
89 | },
90 | {
91 | category: "TensorFlow",
92 | icon: "google"
93 | },
94 | {
95 | category: "Keras",
96 | icon: "kickstarter-k"
97 | },
98 | {
99 | category: "SciKit"
100 | }
101 | ];
102 |
--------------------------------------------------------------------------------
/styles/contribute.module.scss:
--------------------------------------------------------------------------------
1 | .mainLayout {
2 |
3 | display: flex;
4 | position: relative;
5 | height: 100vh;
6 | overflow: hidden;
7 | align-items: center;
8 |
9 | background-color: var(--background);
10 | color: var(--foreground);
11 |
12 | .goBack {
13 | position: absolute;
14 | left: 10px;
15 | top: 10px;
16 | text-decoration: underline;
17 | display: flex;
18 | cursor: pointer;
19 | z-index: 100;
20 | }
21 |
22 | .markdownBody {
23 | padding: 5rem;
24 | height: 100vh;
25 | overflow: scroll;
26 | }
27 |
28 | .formBody {
29 | background-color: var(--card);
30 | position: relative;
31 | color: var(--foreground);
32 | width: 80%;
33 | height: 100vh;
34 | overflow: hidden;
35 |
36 | height: 100vh;
37 | overflow: scroll;
38 | padding: 4rem 2rem 2rem;
39 | }
40 |
41 | @media only screen and (min-width: 320px) and (max-width: 480px) {
42 | overflow: scroll;
43 | display: block;
44 |
45 | .formBody{
46 | width: 100%;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss/base';
2 | @import 'tailwindcss/components';
3 | @import 'tailwindcss/utilities';
4 | @layer base {
5 | :root {
6 | --background: 0 0% 100%;
7 | --foreground: 0 0% 3.9%;
8 | --card: 0 0% 100%;
9 | --card-foreground: 0 0% 3.9%;
10 | --popover: 0 0% 100%;
11 | --popover-foreground: 0 0% 3.9%;
12 | --primary: 0 0% 9%;
13 | --primary-foreground: 0 0% 98%;
14 | --secondary: 0 0% 96.1%;
15 | --secondary-foreground: 0 0% 9%;
16 | --muted: 0 0% 96.1%;
17 | --muted-foreground: 0 0% 45.1%;
18 | --accent: 0 0% 96.1%;
19 | --accent-foreground: 0 0% 9%;
20 | --destructive: 0 84.2% 60.2%;
21 | --destructive-foreground: 0 0% 98%;
22 | --border: 0 0% 89.8%;
23 | --input: 0 0% 89.8%;
24 | --ring: 0 0% 3.9%;
25 | --chart-1: 12 76% 61%;
26 | --chart-2: 173 58% 39%;
27 | --chart-3: 197 37% 24%;
28 | --chart-4: 43 74% 66%;
29 | --chart-5: 27 87% 67%;
30 | --radius: 0.5rem
31 | }
32 | .dark {
33 | --background: #0d1117;
34 | --foreground: 0 0% 98%;
35 | --card: 0 0% 3.9%;
36 | --card-foreground: 0 0% 98%;
37 | --popover: 0 0% 3.9%;
38 | --popover-foreground: 0 0% 98%;
39 | --primary: 0 0% 98%;
40 | --primary-foreground: 0 0% 9%;
41 | --secondary: 0 0% 14.9%;
42 | --secondary-foreground: 0 0% 98%;
43 | --muted: 0 0% 14.9%;
44 | --muted-foreground: 0 0% 63.9%;
45 | --accent: 0 0% 14.9%;
46 | --accent-foreground: 0 0% 98%;
47 | --destructive: 0 62.8% 30.6%;
48 | --destructive-foreground: 0 0% 98%;
49 | --border: 0 0% 14.9%;
50 | --input: 0 0% 14.9%;
51 | --ring: 0 0% 83.1%;
52 | --chart-1: 220 70% 50%;
53 | --chart-2: 160 60% 45%;
54 | --chart-3: 30 80% 55%;
55 | --chart-4: 280 65% 60%;
56 | --chart-5: 340 75% 55%
57 | }
58 | }
59 | @layer base {
60 | * {
61 | @apply border-border;
62 | }
63 | body {
64 | @apply bg-background text-foreground;
65 | }
66 | }
--------------------------------------------------------------------------------
/styles/markdown-styles.module.scss:
--------------------------------------------------------------------------------
1 | .markdownBody {
2 | font-size: 16px;
3 | line-height: 1.6;
4 | padding: 5rem;
5 | border-left: 1px solid #e1e4e8;
6 |
7 | /* Headings */
8 | h1 {
9 | font-size: 2rem;
10 | margin: 1.5rem 0;
11 | padding-bottom: 0.3em;
12 | border-bottom: 1px solid #eaecef;
13 | }
14 |
15 | h2 {
16 | font-size: 1.5rem;
17 | margin-top: 1rem;
18 | border-bottom: 1px solid #eaecef;
19 | padding-bottom: 0.3em;
20 | }
21 |
22 | h3 {
23 | font-size: 1.25rem;
24 | margin-top: 1rem;
25 | }
26 |
27 | /* Paragraphs */
28 | p {
29 | margin-top: 1rem;
30 | }
31 |
32 | /* Blockquotes */
33 | blockquote {
34 | border-left: 4px solid #dfe2e5;
35 | padding-left: 1rem;
36 | color: #6a737d;
37 | margin: 0;
38 | background-color: #424242;
39 | }
40 |
41 | /* Lists */
42 | ul,
43 | ol {
44 | margin-top: 1rem;
45 | padding-left: 2rem;
46 | }
47 |
48 | li {
49 | margin-bottom: 0.5rem;
50 | }
51 |
52 | /* Inline Code */
53 | code {
54 | font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier,
55 | monospace;
56 | font-size: 85%;
57 | background-color: rgba(27, 31, 35, 0.05);
58 | padding: 0.2rem 0.4rem;
59 | border-radius: 3px;
60 | }
61 |
62 | /* Code blocks */
63 | pre {
64 | background: linear-gradient(
65 | 135deg,
66 | rgba(255, 255, 255, 0.05) 10.93%,
67 | rgba(255, 255, 255, 0) 90%
68 | );
69 | padding: 1rem;
70 | border-radius: 3px;
71 | overflow: auto;
72 | }
73 |
74 | pre > code {
75 | padding: 0;
76 | background-color: transparent;
77 | border: 0;
78 | }
79 |
80 | /* Links */
81 | a {
82 | color: #0366d6;
83 | text-decoration: none;
84 | }
85 |
86 | a:hover {
87 | text-decoration: underline;
88 | }
89 |
90 | /* Tables */
91 | table {
92 | width: 100%;
93 | margin-top: 1rem;
94 | border-collapse: collapse;
95 | }
96 |
97 | th {
98 | padding: 0.6rem;
99 | text-align: left;
100 | border-bottom: 1px solid #dfe2e5;
101 | }
102 |
103 | td {
104 | padding: 0.6rem;
105 | border-bottom: 1px solid #dfe2e5;
106 | }
107 |
108 | /* Images */
109 | img {
110 | max-width: 100%;
111 | height: auto;
112 | }
113 |
114 | @media only screen and (min-width: 320px) and (max-width: 480px) {
115 | padding: 2rem;
116 | border: unset;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: "jit",
3 | darkMode: ["class"],
4 | content: [
5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./src/**/*.{js,ts,jsx,tsx,mdx}",
7 | ],
8 | theme: {
9 | extend: {
10 | borderRadius: {
11 | lg: "var(--radius)",
12 | md: "calc(var(--radius) - 2px)",
13 | sm: "calc(var(--radius) - 4px)",
14 | },
15 | colors: {
16 | background: "hsl(var(--background))",
17 | foreground: "hsl(var(--foreground))",
18 | card: {
19 | DEFAULT: "hsl(var(--card))",
20 | foreground: "hsl(var(--card-foreground))",
21 | },
22 | popover: {
23 | DEFAULT: "hsl(var(--popover))",
24 | foreground: "hsl(var(--popover-foreground))",
25 | },
26 | primary: {
27 | DEFAULT: "hsl(var(--primary))",
28 | foreground: "hsl(var(--primary-foreground))",
29 | },
30 | secondary: {
31 | DEFAULT: "hsl(var(--secondary))",
32 | foreground: "hsl(var(--secondary-foreground))",
33 | },
34 | muted: {
35 | DEFAULT: "hsl(var(--muted))",
36 | foreground: "hsl(var(--muted-foreground))",
37 | },
38 | accent: {
39 | DEFAULT: "hsl(var(--accent))",
40 | foreground: "hsl(var(--accent-foreground))",
41 | },
42 | destructive: {
43 | DEFAULT: "hsl(var(--destructive))",
44 | foreground: "hsl(var(--destructive-foreground))",
45 | },
46 | border: "hsl(var(--border))",
47 | input: "hsl(var(--input))",
48 | ring: "hsl(var(--ring))",
49 | chart: {
50 | 1: "hsl(var(--chart-1))",
51 | 2: "hsl(var(--chart-2))",
52 | 3: "hsl(var(--chart-3))",
53 | 4: "hsl(var(--chart-4))",
54 | 5: "hsl(var(--chart-5))",
55 | },
56 | },
57 | keyframes: {
58 | "accordion-down": {
59 | from: {
60 | height: "0",
61 | },
62 | to: {
63 | height: "var(--radix-accordion-content-height)",
64 | },
65 | },
66 | "accordion-up": {
67 | from: {
68 | height: "var(--radix-accordion-content-height)",
69 | },
70 | to: {
71 | height: "0",
72 | },
73 | },
74 | },
75 | animation: {
76 | "accordion-down": "accordion-down 0.2s ease-out",
77 | "accordion-up": "accordion-up 0.2s ease-out",
78 | },
79 | },
80 | },
81 | plugins: [require("tailwindcss-animate")],
82 | };
83 |
--------------------------------------------------------------------------------