├── 4_news_app ├── app │ ├── (auth) │ │ ├── user │ │ │ ├── signup │ │ │ │ ├── signup.module.css │ │ │ │ └── page.tsx │ │ │ └── login │ │ │ │ ├── login.module.css │ │ │ │ └── page.tsx │ │ ├── layout.module.css │ │ └── layout.tsx │ ├── favicon.ico │ ├── loading.tsx │ ├── (main) │ │ ├── page.tsx │ │ ├── news-list │ │ │ ├── layout.module.css │ │ │ ├── [[...slug]] │ │ │ │ ├── loading-deleted.tsx │ │ │ │ ├── news-list.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ ├── news-list.module.css │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── layout.tsx │ │ ├── @latestus │ │ │ └── page.tsx │ │ ├── categories │ │ │ └── page.tsx │ │ ├── admin │ │ │ └── page.tsx │ │ ├── add-news │ │ │ ├── add-article.module.css │ │ │ └── page.tsx │ │ ├── @latestgb │ │ │ └── page.tsx │ │ └── news │ │ │ └── [slug] │ │ │ ├── page.tsx │ │ │ └── article.module.css │ ├── not-found.tsx │ ├── api │ │ ├── news │ │ │ ├── [slug] │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ ├── route.ts │ │ └── auth │ │ │ └── login │ │ │ └── route.ts │ ├── error.tsx │ ├── layout.tsx │ └── globals.css ├── components │ ├── article-item │ │ ├── article-item.module.css │ │ └── ArticleItem.tsx │ ├── categories │ │ ├── categories.module.css │ │ └── Categories.tsx │ ├── latest-news │ │ ├── item │ │ │ ├── item.module.css │ │ │ └── Item.tsx │ │ ├── latest-news.module.css │ │ └── LatestNews.tsx │ ├── add-article │ │ ├── SubmitArticle.tsx │ │ ├── add-article.module.css │ │ └── AddArticle.tsx │ ├── hero │ │ ├── Hero.tsx │ │ └── hero.module.css │ ├── header │ │ ├── NavLink.tsx │ │ ├── Header.tsx │ │ └── header.module.css │ ├── category │ │ ├── Category.tsx │ │ └── category.module.css │ └── auth │ │ └── login-form │ │ └── login.module.css ├── news.db ├── public │ ├── n1.jpg │ ├── cat1.png │ ├── cat2.png │ ├── cat3.jpg │ ├── logo.png │ ├── cats │ │ ├── gaza.webp │ │ ├── finance.webp │ │ ├── global.webp │ │ ├── sports.webp │ │ ├── weather.webp │ │ ├── palestine.webp │ │ └── westbank.webp │ ├── vercel.svg │ ├── images │ │ ├── customer-accounts-officer.jpg │ │ ├── corporate-communications-strategist.jpg │ │ └── international-identity-orchestrator.jpg │ ├── window.svg │ ├── file.svg │ ├── globe.svg │ └── next.svg ├── .env ├── eslint.config.mjs ├── services │ ├── auth.ts │ └── news.service.ts ├── .gitignore ├── tsconfig.json ├── next.config.ts ├── constants │ └── data.ts ├── package.json ├── types │ └── index.d.ts ├── utils │ └── auth.ts ├── README.md ├── middleware.ts └── controllers │ └── news-actions.ts ├── 1_Students App ├── src │ ├── components │ │ ├── courses-list │ │ │ ├── courses-list.css │ │ │ └── courses-list.component.tsx │ │ ├── nav-bar │ │ │ ├── nav-bar.css │ │ │ └── nav-bar.component.tsx │ │ ├── student │ │ │ ├── student.css │ │ │ └── student.component.tsx │ │ ├── common │ │ │ └── guarded-route │ │ │ │ └── guarded-route.component.tsx │ │ ├── courses-list-form │ │ │ └── courses-list-form.component.tsx │ │ ├── add-form │ │ │ ├── add-form.css │ │ │ └── add-form.component.tsx │ │ └── absents │ │ │ └── absents.component.tsx │ ├── vite-env.d.ts │ ├── screens │ │ ├── NotFound.screen.tsx │ │ ├── About.screen.tsx │ │ ├── AddStudent.screen.tsx │ │ ├── StudentDetails.screen.tsx │ │ └── Login.screen.tsx │ ├── @types.ts │ ├── main.tsx │ ├── utils │ │ └── validation.ts │ ├── providers │ │ ├── authProvider.tsx │ │ └── stateProvider.tsx │ ├── hooks │ │ └── local-storage.hook.ts │ ├── App.tsx │ ├── state │ │ └── reducer.ts │ ├── App.css │ ├── index.css │ └── assets │ │ └── react.svg ├── tsconfig.json ├── vite.config.ts ├── .gitignore ├── index.html ├── tsconfig.node.json ├── tsconfig.app.json ├── package.json ├── eslint.config.js ├── public │ └── vite.svg └── README.md ├── 2_Todo App ├── src │ ├── vite-env.d.ts │ ├── components │ │ ├── types.ts │ │ ├── todo-list │ │ │ ├── todo-list.css │ │ │ └── todo-list.component.tsx │ │ ├── form │ │ │ ├── form.css │ │ │ └── form.component.tsx │ │ ├── dashboard │ │ │ ├── dashboard.css │ │ │ └── dashboard-component.tsx │ │ └── todo-item │ │ │ ├── todo-item.component.tsx │ │ │ └── todo-item.css │ ├── App.css │ ├── hooks │ │ └── local-storage.hook.ts │ ├── main.tsx │ ├── state │ │ └── reducer.ts │ ├── App.tsx │ └── assets │ │ └── react.svg ├── tsconfig.json ├── vite.config.ts ├── .gitignore ├── index.html ├── tsconfig.node.json ├── tsconfig.app.json ├── package.json ├── eslint.config.js ├── public │ └── vite.svg └── README.md ├── testing-ui ├── src │ ├── vite-env.d.ts │ ├── main.tsx │ ├── App.css │ ├── index.css │ ├── App.tsx │ └── assets │ │ └── react.svg ├── tsconfig.json ├── vite.config.ts ├── .gitignore ├── index.html ├── tsconfig.node.json ├── tsconfig.app.json ├── package.json ├── eslint.config.js ├── public │ └── vite.svg └── README.md ├── memory-card-game ├── src │ ├── vite-env.d.ts │ ├── main.tsx │ ├── screens │ │ ├── not-found.screen.tsx │ │ ├── game.screen.tsx │ │ ├── screens.css │ │ ├── levels.screen.tsx │ │ └── score-board.screen.tsx │ ├── components │ │ ├── score-list │ │ │ ├── score-list.css │ │ │ └── score-list.tsx │ │ ├── congrats │ │ │ ├── congrats.tsx │ │ │ └── congrats.css │ │ ├── status-bar │ │ │ ├── status-bar.css │ │ │ └── status-bar.tsx │ │ ├── card-list │ │ │ ├── card-list.css │ │ │ └── card-list.tsx │ │ └── card │ │ │ ├── card.tsx │ │ │ └── card.css │ ├── types │ │ └── @types.ts │ ├── utils │ │ └── game.util.ts │ ├── App.tsx │ ├── providers │ │ ├── modeProvider.tsx │ │ └── reducer.ts │ ├── App.css │ └── hooks │ │ └── game-logic.hook.ts ├── tsconfig.json ├── vite.config.ts ├── screens.txt ├── .gitignore ├── index.html ├── tsconfig.node.json ├── tsconfig.app.json ├── package.json ├── eslint.config.js ├── public │ └── vite.svg └── README.md └── 3_next-example ├── app ├── favicon.ico ├── reach │ ├── contact │ │ └── page.tsx │ ├── layout.module.css │ ├── layout.tsx │ ├── about │ │ └── page.tsx │ └── portfolio │ │ └── page.tsx ├── page.module.css ├── page.tsx ├── globals.css ├── layout.tsx └── antd │ └── page.tsx ├── postcss.config.mjs ├── public ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── next.config.ts ├── tailwind.config.ts ├── components ├── Form.tsx └── main-header │ ├── main-header.module.css │ └── MainHeader.tsx ├── eslint.config.mjs ├── .gitignore ├── tsconfig.json ├── package.json └── README.md /4_news_app/app/(auth)/user/signup/signup.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /4_news_app/components/article-item/article-item.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /1_Students App/src/components/courses-list/courses-list.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /2_Todo App/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /testing-ui/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /1_Students App/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /memory-card-game/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /4_news_app/app/(auth)/layout.module.css: -------------------------------------------------------------------------------- 1 | .layout { 2 | padding-top: 50px; 3 | } -------------------------------------------------------------------------------- /4_news_app/news.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/news.db -------------------------------------------------------------------------------- /4_news_app/public/n1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/n1.jpg -------------------------------------------------------------------------------- /4_news_app/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/app/favicon.ico -------------------------------------------------------------------------------- /4_news_app/public/cat1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/cat1.png -------------------------------------------------------------------------------- /4_news_app/public/cat2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/cat2.png -------------------------------------------------------------------------------- /4_news_app/public/cat3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/cat3.jpg -------------------------------------------------------------------------------- /4_news_app/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/logo.png -------------------------------------------------------------------------------- /3_next-example/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/3_next-example/app/favicon.ico -------------------------------------------------------------------------------- /4_news_app/public/cats/gaza.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/cats/gaza.webp -------------------------------------------------------------------------------- /4_news_app/.env: -------------------------------------------------------------------------------- 1 | # YOU MUST NEVER COMMIT THIS FILE TO GIT 2 | JWT_SECRET = 'as;falklk;fasdoiuew$%#TF#$SDafdafsd@#4324231FGA43' -------------------------------------------------------------------------------- /4_news_app/public/cats/finance.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/cats/finance.webp -------------------------------------------------------------------------------- /4_news_app/public/cats/global.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/cats/global.webp -------------------------------------------------------------------------------- /4_news_app/public/cats/sports.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/cats/sports.webp -------------------------------------------------------------------------------- /4_news_app/public/cats/weather.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/cats/weather.webp -------------------------------------------------------------------------------- /4_news_app/public/cats/palestine.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/cats/palestine.webp -------------------------------------------------------------------------------- /4_news_app/public/cats/westbank.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/cats/westbank.webp -------------------------------------------------------------------------------- /3_next-example/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | "@tailwindcss/postcss": {} 4 | }, 5 | }; 6 | export default config; 7 | -------------------------------------------------------------------------------- /2_Todo App/src/components/types.ts: -------------------------------------------------------------------------------- 1 | export interface ITodoItem { 2 | id: number; 3 | title: string; 4 | isUrgent: boolean; 5 | isDone: boolean; 6 | } -------------------------------------------------------------------------------- /4_news_app/components/categories/categories.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | justify-content: space-between; 4 | flex-wrap: wrap; 5 | } -------------------------------------------------------------------------------- /4_news_app/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /3_next-example/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /4_news_app/public/images/customer-accounts-officer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/images/customer-accounts-officer.jpg -------------------------------------------------------------------------------- /2_Todo App/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /testing-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /1_Students App/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /memory-card-game/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /memory-card-game/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import App from './App.tsx' 3 | 4 | createRoot(document.getElementById('root')!).render() 5 | -------------------------------------------------------------------------------- /4_news_app/public/images/corporate-communications-strategist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/images/corporate-communications-strategist.jpg -------------------------------------------------------------------------------- /4_news_app/public/images/international-identity-orchestrator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kldoon/GSG_React_Next/HEAD/4_news_app/public/images/international-identity-orchestrator.jpg -------------------------------------------------------------------------------- /3_next-example/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /memory-card-game/src/screens/not-found.screen.tsx: -------------------------------------------------------------------------------- 1 | const NotFound = () => { 2 | return ( 3 |
The page you requested doesn't exists
4 | ) 5 | } 6 | 7 | export default NotFound; -------------------------------------------------------------------------------- /1_Students App/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /2_Todo App/src/components/todo-list/todo-list.css: -------------------------------------------------------------------------------- 1 | .list-wrapper { 2 | border: 1px solid black; 3 | margin: 20px; 4 | padding: 10px; 5 | display: flex; 6 | flex-direction: column; 7 | row-gap: 10px; 8 | } -------------------------------------------------------------------------------- /2_Todo App/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /testing-ui/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /memory-card-game/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /4_news_app/app/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const GlobalLoadingPage = () => { 4 | return ( 5 |
6 |

Loading ...

7 |
8 | ) 9 | } 10 | 11 | export default GlobalLoadingPage; -------------------------------------------------------------------------------- /2_Todo App/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 3 | } 4 | 5 | h1 { 6 | font-size: 16px; 7 | } 8 | 9 | h1.light { 10 | color: #000; 11 | } 12 | 13 | h1.dark { 14 | color: #fff; 15 | } -------------------------------------------------------------------------------- /3_next-example/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | export default { 4 | content: [ 5 | './app/**/*.{js,ts,jsx,tsx,mdx}' 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } satisfies Config -------------------------------------------------------------------------------- /memory-card-game/src/components/score-list/score-list.css: -------------------------------------------------------------------------------- 1 | ul.score-list { 2 | list-style-type: none; 3 | padding: 0; 4 | margin: 0; 5 | text-align: left; 6 | } 7 | 8 | ul.score-list li { 9 | display: flex; 10 | justify-content: space-between; 11 | } -------------------------------------------------------------------------------- /1_Students App/src/screens/NotFound.screen.tsx: -------------------------------------------------------------------------------- 1 | const NotFound = () => { 2 | return ( 3 | <> 4 |

Page Not found (404)

5 |

6 | We can't find the page you are looking for 7 |

8 | 9 | ) 10 | } 11 | 12 | export default NotFound -------------------------------------------------------------------------------- /3_next-example/app/reach/contact/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Form from '@/components/Form'; 3 | 4 | const Contact = () => { 5 | return ( 6 |
7 |

Contact Us

8 |
9 |
10 | ) 11 | } 12 | 13 | export default Contact; -------------------------------------------------------------------------------- /4_news_app/app/(main)/page.tsx: -------------------------------------------------------------------------------- 1 | import Hero from "@/components/hero/Hero"; 2 | import Categories from "@/components/categories/Categories"; 3 | 4 | export default function Home() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /testing-ui/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.tsx' 5 | 6 | createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /4_news_app/app/(main)/news-list/layout.module.css: -------------------------------------------------------------------------------- 1 | .layout { 2 | display: flex; 3 | align-items: stretch; 4 | column-gap: 30px; 5 | } 6 | 7 | .sideBar { 8 | min-height: 100vh; 9 | flex-basis: 25%; 10 | flex-shrink: 0; 11 | box-shadow: 0px 0px 1px #171a1f1F, 0px 0px 2px #171a1f1F; 12 | } -------------------------------------------------------------------------------- /4_news_app/app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './layout.module.css'; 3 | 4 | export default function AuthLayout({ children }: Readonly<{ children: React.ReactNode; }>) { 5 | return ( 6 |
7 | {children} 8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /4_news_app/app/(main)/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from "@/components/header/Header"; 3 | 4 | export default function MainLayout({ children }: Readonly<{ children: React.ReactNode; }>) { 5 | return ( 6 |
7 |
8 | {children} 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /memory-card-game/src/components/congrats/congrats.tsx: -------------------------------------------------------------------------------- 1 | import './congrats.css' 2 | 3 | const Congrats = () => { 4 | return ( 5 |
6 |

Congratulations You WON the game 🤑😛

7 |

You will be redirected to the ScoreBoard shortly

8 |
9 | ) 10 | } 11 | 12 | export default Congrats; -------------------------------------------------------------------------------- /4_news_app/app/(auth)/user/signup/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classes from "./signup.module.css"; 3 | 4 | const Page = () => { 5 | return ( 6 |
7 |

Signup to new account in GSG News APP

8 |
9 | ) 10 | } 11 | 12 | export default Page; -------------------------------------------------------------------------------- /4_news_app/app/(main)/news-list/[[...slug]]/loading-deleted.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './news-list.module.css'; 3 | 4 | const NewLoadingPage = () => { 5 | return ( 6 |
7 |
8 |
9 | ) 10 | } 11 | 12 | export default NewLoadingPage; -------------------------------------------------------------------------------- /3_next-example/components/Form.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Form = () => { 4 | return ( 5 | 6 | Please enter all data 7 |
8 |
9 |
10 | 11 | 12 | ) 13 | } 14 | 15 | export default Form; -------------------------------------------------------------------------------- /memory-card-game/screens.txt: -------------------------------------------------------------------------------- 1 | - Login Screen 2 | - Levels Screen 3 | - Level Component 4 | - Tutorial Component 5 | - Game Screen 6 | - Card Component 7 | - Cards List Component 8 | - Status Component (name, level, score, elapsed time, moves, retry) 9 | - Score Board 10 | - Score List Component 11 | - Score Item Component 12 | - Top 3 Component 13 | 14 | -------------------------------------------------------------------------------- /2_Todo App/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /testing-ui/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /1_Students App/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /memory-card-game/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /memory-card-game/src/types/@types.ts: -------------------------------------------------------------------------------- 1 | export enum ELevels { 2 | EASY = 2, 3 | MEDIUM = 4, 4 | HARD = 6 5 | } 6 | 7 | export interface ICard { 8 | id: number; 9 | image: string; 10 | visible: boolean; 11 | revealed: boolean; 12 | } 13 | 14 | export interface IScore { 15 | playerName: string; 16 | finishTime: number; 17 | wrongMoves: number; 18 | level: ELevels; 19 | } -------------------------------------------------------------------------------- /1_Students App/src/components/courses-list/courses-list.component.tsx: -------------------------------------------------------------------------------- 1 | 2 | interface IProps { 3 | list: string[]; 4 | } 5 | 6 | const CoursesList = (props: IProps) => { 7 | return ( 8 |
    9 | { 10 | props.list.map((item, index) =>
  • {item}
  • ) 11 | } 12 |
13 | ) 14 | } 15 | 16 | export default CoursesList; -------------------------------------------------------------------------------- /1_Students App/src/@types.ts: -------------------------------------------------------------------------------- 1 | export interface IStudent { 2 | id: string; 3 | name: string; 4 | age: number; 5 | absents: number; 6 | isGraduated: boolean; 7 | coursesList: string[]; 8 | } 9 | 10 | export interface IUserData { 11 | userName: string; 12 | role: Role; 13 | } 14 | 15 | export enum Role { 16 | ADMIN = 'admin', 17 | Teacher = 'teacher', 18 | GUEST = 'guest' 19 | } -------------------------------------------------------------------------------- /4_news_app/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /3_next-example/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /4_news_app/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /3_next-example/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /memory-card-game/src/components/status-bar/status-bar.css: -------------------------------------------------------------------------------- 1 | .status-bar { 2 | display: flex; 3 | justify-content: space-between; 4 | font-size: 25px; 5 | font-weight: bold; 6 | font-family: 'Courier New', Courier, monospace; 7 | background: linear-gradient(360deg, #d9d9d9, #d8d7ffb0); 8 | width: 75vmin; 9 | padding: 23px; 10 | border-radius: 20px; 11 | box-shadow: 2px 2px 1px 0px #c6c6c6; 12 | } -------------------------------------------------------------------------------- /3_next-example/app/reach/layout.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: flex-start; 5 | } 6 | 7 | .wrapper>aside { 8 | flex-basis: 20vw; 9 | background-color: #ccc; 10 | min-height: 70vh; 11 | padding-top: 30px; 12 | padding-left: 10px; 13 | } 14 | 15 | .wrapper>main { 16 | flex-basis: 80vw; 17 | min-height: 70vh; 18 | border: 1px solid gray; 19 | } -------------------------------------------------------------------------------- /2_Todo App/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /4_news_app/app/(main)/news-list/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './layout.module.css'; 3 | 4 | export default function NewsLayout({ 5 | children, 6 | }: Readonly<{ 7 | children: React.ReactNode; 8 | }>) { 9 | return ( 10 |
11 |
12 | side bar 13 |
14 | {children} 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /testing-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /1_Students App/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /memory-card-game/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /3_next-example/components/main-header/main-header.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | justify-content: space-between; 4 | background-color: aquamarine; 5 | padding: 20px 30px; 6 | } 7 | 8 | .main-nav>ul { 9 | display: flex; 10 | column-gap: 10px; 11 | list-style-type: none; 12 | padding: 0; 13 | margin: 0; 14 | } 15 | 16 | .main-nav>ul>li { 17 | padding: 0; 18 | margin: 10px 0; 19 | padding-right: 10px; 20 | border-right: 1px solid #aaa; 21 | } -------------------------------------------------------------------------------- /4_news_app/components/categories/Categories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './categories.module.css'; 3 | import Category from '../category/Category'; 4 | import { CATEGORIES } from '@/constants/data'; 5 | 6 | const Categories = () => { 7 | return ( 8 |
9 | { 10 | CATEGORIES.map(cat => ) 11 | } 12 |
13 | ) 14 | } 15 | 16 | export default Categories -------------------------------------------------------------------------------- /2_Todo App/src/components/form/form.css: -------------------------------------------------------------------------------- 1 | .form-wrapper { 2 | margin: 20px; 3 | padding: 10px; 4 | display: flex; 5 | flex-direction: column; 6 | row-gap: 10px; 7 | } 8 | 9 | .form-wrapper .task-input { 10 | flex-basis: 100%; 11 | padding: 10px; 12 | } 13 | 14 | .form-wrapper .submit { 15 | padding: 10px; 16 | } 17 | 18 | .form-wrapper.light { 19 | border: 1px solid black; 20 | } 21 | 22 | .form-wrapper.dark { 23 | border: 1px solid white; 24 | color: white; 25 | } -------------------------------------------------------------------------------- /3_next-example/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /4_news_app/app/(main)/@latestus/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LatestNews from '@/components/latest-news/LatestNews'; 3 | 4 | import { fetchNews } from '@/services/news.service'; 5 | 6 | const Page = async () => { 7 | const latestNews: News.Item[] = await fetchNews('politics', 'us') as News.Item[]; 8 | 9 | return ( 10 |
11 | 15 |
16 | ) 17 | } 18 | 19 | export default Page; -------------------------------------------------------------------------------- /4_news_app/app/(main)/categories/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | import Categories from '@/components/categories/Categories'; 3 | import { Metadata } from 'next'; 4 | // import Link from 'next/link'; 5 | import React from 'react'; 6 | 7 | export const metadata: Metadata = { 8 | title: 'Categories!', 9 | description: 'GSG News website, News Categories!' 10 | } 11 | 12 | const Page = () => { 13 | return ( 14 |
15 |

Categories Page

16 | 17 |
18 | ) 19 | } 20 | 21 | export default Page; -------------------------------------------------------------------------------- /4_news_app/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript") 14 | //@typescript-eslint/no-unused-vars 15 | ]; 16 | 17 | export default eslintConfig; 18 | -------------------------------------------------------------------------------- /memory-card-game/src/components/card-list/card-list.css: -------------------------------------------------------------------------------- 1 | .card-list { 2 | display: grid; 3 | gap: 10px; 4 | width: 100%; 5 | max-width: 75vmin; 6 | } 7 | 8 | .card-list.level_2 { 9 | grid-template-columns: repeat(2, 1fr); 10 | grid-template-rows: repeat(2, 1fr); 11 | } 12 | 13 | .card-list.level_4 { 14 | grid-template-columns: repeat(4, 1fr); 15 | grid-template-rows: repeat(4, 1fr); 16 | } 17 | 18 | .card-list.level_6 { 19 | grid-template-columns: repeat(6, 1fr); 20 | grid-template-rows: repeat(6, 1fr); 21 | } -------------------------------------------------------------------------------- /3_next-example/app/page.module.css: -------------------------------------------------------------------------------- 1 | .page { 2 | --gray-rgb: 0, 0, 0; 3 | --gray-alpha-200: rgba(var(--gray-rgb), 0.08); 4 | --gray-alpha-100: rgba(var(--gray-rgb), 0.05); 5 | 6 | --button-primary-hover: #383838; 7 | --button-secondary-hover: #f2f2f2; 8 | 9 | display: flex; 10 | flex-direction: column; 11 | justify-items: center; 12 | min-height: 100svh; 13 | padding: 80px; 14 | gap: 20px; 15 | font-family: var(--font-geist-sans); 16 | } 17 | 18 | .page nav { 19 | display: flex; 20 | column-gap: 20px; 21 | } -------------------------------------------------------------------------------- /4_news_app/components/latest-news/item/item.module.css: -------------------------------------------------------------------------------- 1 | .newsItem { 2 | flex: 0 0 23%; 3 | align-items: stretch; 4 | padding: 10px; 5 | cursor: pointer; 6 | box-sizing: border-box; 7 | box-shadow: 0px 0px 4px #ddd, 0px 0px 5px #eee; 8 | transition: transform 0.1s linear; 9 | background: #fff; 10 | } 11 | 12 | .newsItem.highlighted { 13 | transform: scale(1.1); 14 | box-shadow: 0 0 5 5 #dcdcdc; 15 | } 16 | 17 | .newsItem .info .button { 18 | padding: 0px 10px; 19 | font-size: 12px; 20 | } -------------------------------------------------------------------------------- /1_Students App/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import './index.css'; 3 | import App from './App'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import { AuthProvider } from './providers/authProvider'; 6 | import StateProvider from './providers/stateProvider'; 7 | 8 | createRoot(document.getElementById('root')!).render( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /1_Students App/src/screens/About.screen.tsx: -------------------------------------------------------------------------------- 1 | const About = () => { 2 | return ( 3 |
4 |

About Students App

5 |

6 | This is an app that we use to explain topics of react in GSG training.
7 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui corrupti optio omnis soluta nam quas accusamus magni. Ipsa quos ipsam quae voluptas tempore quaerat, quidem veniam illum, eius autem cumque? 8 |

9 |
10 | ) 11 | } 12 | 13 | export default About; -------------------------------------------------------------------------------- /4_news_app/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import img404 from '@/public/404.svg'; 3 | import Image from 'next/image'; 4 | import Link from 'next/link'; 5 | 6 | const NotFound = () => { 7 | return ( 8 |
9 |

The page you are looking for is not found!

10 | 404 11 | Go to Home Page 12 |
13 | ) 14 | } 15 | 16 | export default NotFound; -------------------------------------------------------------------------------- /4_news_app/components/add-article/SubmitArticle.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import classes from './add-article.module.css'; 5 | import { useFormStatus } from 'react-dom'; 6 | 7 | const SubmitArticle = () => { 8 | const { pending } = useFormStatus(); 9 | return ( 10 | 17 | ) 18 | } 19 | 20 | export default SubmitArticle; -------------------------------------------------------------------------------- /4_news_app/app/(main)/admin/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Page = () => { 4 | return ( 5 |
6 |

Admin Page

7 |

This is very important admin stuff, you need to have an admin role to see it.

8 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quam, voluptatem cumque exercitationem, dolores, fugiat aspernatur rem hic incidunt maxime sit officia corporis eveniet quasi eligendi dicta natus aperiam. Velit, quaerat.

9 |
10 | ) 11 | } 12 | 13 | export default Page; -------------------------------------------------------------------------------- /4_news_app/app/(auth)/user/login/login.module.css: -------------------------------------------------------------------------------- 1 | .loginContainer { 2 | max-width: 450px; 3 | margin: 2rem auto; 4 | padding: 2.5rem; 5 | background-color: white; 6 | border-radius: 8px; 7 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); 8 | } 9 | 10 | .loginTitle { 11 | color: #71b2ab; 12 | margin-bottom: 0.5rem; 13 | font-size: 2rem; 14 | font-weight: 600; 15 | border-bottom: 2px solid #a3d0ca; 16 | padding-bottom: 0.5rem; 17 | } 18 | 19 | .loginDescription { 20 | margin-bottom: 1.5rem; 21 | color: #666; 22 | font-size: 1rem; 23 | } -------------------------------------------------------------------------------- /4_news_app/app/(main)/add-news/add-article.module.css: -------------------------------------------------------------------------------- 1 | .newsFormContainer { 2 | max-width: 800px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | background-color: white; 6 | border-radius: 8px; 7 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); 8 | } 9 | 10 | .formTitle { 11 | color: #71b2ab; 12 | margin-bottom: 0.5rem; 13 | font-size: 2rem; 14 | font-weight: 600; 15 | border-bottom: 2px solid #a3d0ca; 16 | padding-bottom: 0.5rem; 17 | } 18 | 19 | .formDescription { 20 | margin-bottom: 1.5rem; 21 | color: #666; 22 | font-size: 1rem; 23 | } -------------------------------------------------------------------------------- /4_news_app/app/api/news/[slug]/route.ts: -------------------------------------------------------------------------------- 1 | import { getNewsArticle } from "@/services/news.service"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | const GET = async (request: NextRequest, { params }: { params: { slug: string } }) => { 5 | const slug = (await params).slug; 6 | 7 | const article = getNewsArticle(slug); 8 | if (!article) { 9 | return new NextResponse('Article Not found', { status: 404 }); 10 | } 11 | 12 | return NextResponse.json( 13 | article, 14 | { status: 200 } 15 | ); 16 | } 17 | 18 | export { GET }; -------------------------------------------------------------------------------- /1_Students App/src/screens/AddStudent.screen.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import AddForm from "../components/add-form/add-form.component"; 3 | import { StateContext } from "../providers/stateProvider"; 4 | 5 | const AddStudent = () => { 6 | const { dispatch } = useContext(StateContext); 7 | 8 | return ( 9 |
10 |

Add New Student

11 | dispatch({ type: "ADD_STUDENT", payload: newStudent })} /> 12 |
13 | ) 14 | } 15 | 16 | export default AddStudent; -------------------------------------------------------------------------------- /4_news_app/services/auth.ts: -------------------------------------------------------------------------------- 1 | import sql from 'better-sqlite3'; 2 | const db = sql('news.db'); 3 | 4 | const findUserByEmail = (email: string) => { 5 | const results = db.prepare('SELECT * FROM users WHERE email = ?').get(email); 6 | return results as News.IUser; 7 | } 8 | 9 | const createUser = (user: News.IUser) => { 10 | console.log(user); 11 | /* 12 | This function will 13 | 1- Validate 14 | 2- Check if email already exists 15 | 3- Hash the password 16 | 4- insert into db 17 | */ 18 | } 19 | 20 | export { 21 | findUserByEmail, 22 | createUser 23 | } -------------------------------------------------------------------------------- /4_news_app/components/hero/Hero.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './hero.module.css'; 3 | 4 | const Hero = () => { 5 | return ( 6 |
7 |

Stay Informed, Stay Ahead

8 | Your go-to platform for the latest and most relevant news articles. 9 |
10 |
Post a News
11 |
Read News
12 | {/* outline */} 13 |
14 |
15 | ) 16 | } 17 | 18 | export default Hero -------------------------------------------------------------------------------- /4_news_app/app/(main)/@latestgb/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LatestNews from '@/components/latest-news/LatestNews'; 3 | 4 | import { fetchNews } from '@/services/news.service'; 5 | 6 | const Page = async () => { 7 | const latestNews: News.Item[] = await fetchNews('politics', 'gb') as News.Item[]; 8 | 9 | if (latestNews.length === 0) return null; 10 | 11 | return ( 12 |
13 | 17 |
18 | ) 19 | } 20 | 21 | export default Page; -------------------------------------------------------------------------------- /4_news_app/app/(main)/news-list/[[...slug]]/news-list.tsx: -------------------------------------------------------------------------------- 1 | import { getNewsByCategory } from '@/services/news.service'; 2 | import ArticleItem from '@/components/article-item/ArticleItem'; 3 | 4 | const NewsList = async ({ category }: { category: string }) => { 5 | const latestNews: News.Item_[] = getNewsByCategory(category); 6 | 7 | return ( 8 |
9 | { 10 | latestNews.map(item => ) 11 | } 12 |
13 | ) 14 | }; 15 | 16 | export default NewsList; -------------------------------------------------------------------------------- /4_news_app/app/(main)/news-list/[[...slug]]/not-found.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import img404 from '@/public/404.svg'; 3 | import Image from 'next/image'; 4 | import Link from 'next/link'; 5 | 6 | const NotFound = () => { 7 | return ( 8 |
9 |

The country or category you are looking for doesn't exist!

10 | 404 11 | Go to categories page 12 |
13 | ) 14 | } 15 | 16 | export default NotFound; -------------------------------------------------------------------------------- /4_news_app/components/header/NavLink.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React from 'react'; 3 | import Link from 'next/link'; 4 | import { usePathname } from 'next/navigation'; 5 | import classes from './header.module.css'; 6 | 7 | interface IProps { 8 | href: string; 9 | children: React.ReactNode; 10 | } 11 | 12 | const NavLink = (props: IProps) => { 13 | const path = usePathname(); 14 | return ( 15 | 19 | {props.children} 20 | 21 | ) 22 | } 23 | 24 | export default NavLink -------------------------------------------------------------------------------- /1_Students App/src/components/nav-bar/nav-bar.css: -------------------------------------------------------------------------------- 1 | 2 | nav { 3 | margin: 20px 0; 4 | border-bottom: 2px solid #DA498D; 5 | display: flex; 6 | justify-content: space-between; 7 | align-items: center; 8 | } 9 | 10 | nav span { 11 | display: flex; 12 | column-gap: 10px; 13 | align-items: center; 14 | } 15 | 16 | nav a { 17 | padding: 3px; 18 | font-size: 18px; 19 | font-weight: 400; 20 | /* TODO: selected tab */ 21 | } 22 | 23 | nav a:hover { 24 | text-shadow: 0px 0px 2px rgb(105, 36, 124); 25 | color: #93729d; 26 | } 27 | 28 | nav a.active { 29 | border-bottom: 3px solid #DA498D; 30 | } 31 | -------------------------------------------------------------------------------- /4_news_app/app/api/route.ts: -------------------------------------------------------------------------------- 1 | export async function GET() { 2 | console.log("Hello from the '/' route"); 3 | 4 | // return new Response("Hello from the '/' route", { status: 200 }); 5 | return Response.json({ message: "Hello from the '/' route" }, { 6 | status: 404 7 | }); 8 | } 9 | 10 | 11 | 12 | 13 | // api/ 14 | 15 | // Better convention 16 | // [GET] /api/news 17 | // [GET] /api/news/city-marathon-2025 18 | // [POST] /api/news 19 | // [PUT] /api/news 20 | // [DELETE] /api/news 21 | 22 | // Bad convention 23 | // api/get-news 24 | // api/add-news 25 | // api/update-news 26 | // api/delete-news 27 | 28 | // api/users 29 | -------------------------------------------------------------------------------- /1_Students App/src/utils/validation.ts: -------------------------------------------------------------------------------- 1 | import { IStudent } from "../@types"; 2 | 3 | const validateStudent = (newStudent: IStudent) => { 4 | const errors: string[] = []; 5 | // Validate the object before sending it. 6 | if (newStudent.name.length < 3) { 7 | errors.push("The name must be more than 3 letters"); 8 | } 9 | 10 | if (newStudent.age < 17 || newStudent.age > 40) { 11 | errors.push("The age must be between 17 and 40"); 12 | } 13 | 14 | if (newStudent.coursesList.length <= 0) { 15 | errors.push("You must add at least one course"); 16 | } 17 | 18 | return errors 19 | } 20 | 21 | export { 22 | validateStudent 23 | } -------------------------------------------------------------------------------- /2_Todo App/src/components/dashboard/dashboard.css: -------------------------------------------------------------------------------- 1 | .dashboard-wrapper { 2 | border: 1px solid black; 3 | margin: 20px; 4 | padding: 10px; 5 | column-gap: 10px; 6 | display: flex; 7 | justify-content: space-evenly; 8 | } 9 | 10 | .dashboard-wrapper>div { 11 | display: flex; 12 | column-gap: 5px; 13 | border: 1px solid black; 14 | padding: 10px; 15 | width: 100%; 16 | } 17 | 18 | .dashboard-wrapper.light { 19 | background-color: rgb(255, 255, 255); 20 | } 21 | 22 | .dashboard-wrapper.dark { 23 | background-color: rgb(70, 70, 70); 24 | color: #fff; 25 | } 26 | 27 | .dashboard-wrapper.dark>div { 28 | border-color: #fff; 29 | } -------------------------------------------------------------------------------- /4_news_app/app/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | interface IProps { 4 | error: Error; 5 | reset: () => void; 6 | } 7 | 8 | const Error = (props: IProps) => { 9 | 10 | return ( 11 |
12 |

Opps!!!

13 |

Something went wrong while processing your request!

14 |

You can or later. 15 |

16 |

Contact the system administrator and provide the following info: {props.error.message}

17 |
18 | ) 19 | } 20 | 21 | export default Error -------------------------------------------------------------------------------- /1_Students App/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "noUncheckedSideEffectImports": true 20 | }, 21 | "include": ["vite.config.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /4_news_app/app/(main)/news-list/[[...slug]]/news-list.module.css: -------------------------------------------------------------------------------- 1 | .spinner { 2 | border: 8px solid #f3f3f3; 3 | border-top: 8px solid #3498db; 4 | border-radius: 50%; 5 | width: 50px; 6 | height: 50px; 7 | animation: spin 2s linear infinite; 8 | } 9 | 10 | @keyframes spin { 11 | 0% { 12 | transform: rotate(0deg); 13 | } 14 | 15 | 100% { 16 | transform: rotate(360deg); 17 | } 18 | } 19 | 20 | h1.header { 21 | text-transform: capitalize; 22 | font-family: var(--font-roboto); 23 | font-size: 24px; 24 | line-height: 36px; 25 | font-weight: 700; 26 | color: #000; 27 | } 28 | 29 | .newsLoading { 30 | overflow: hidden; 31 | } -------------------------------------------------------------------------------- /memory-card-game/src/components/status-bar/status-bar.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import './status-bar.css'; 4 | import { useContext } from 'react'; 5 | import { GameModeContext } from '../../providers/modeProvider'; 6 | 7 | const StatusBar = () => { 8 | const { gameMode } = useContext(GameModeContext); 9 | 10 | return ( 11 |
12 | Level: {Array.from({ length: gameMode.level / 2 }).map(_ => '⭐')} 13 | Time: {gameMode.time}s 14 | Wrong Moves: {gameMode.wrongMoves} 15 |
16 | ) 17 | } 18 | 19 | export default StatusBar; -------------------------------------------------------------------------------- /3_next-example/components/main-header/MainHeader.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import React from 'react'; 3 | import classes from './main-header.module.css'; 4 | 5 | const MainHeader = () => { 6 | return ( 7 |
8 |

First Next Example

9 | 19 |
20 | ) 21 | } 22 | 23 | export default MainHeader -------------------------------------------------------------------------------- /3_next-example/.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.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /3_next-example/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import styles from './page.module.css'; 3 | 4 | const Home = () => { 5 | return ( 6 |
7 |

Hello Next JS

8 |

This is is the first next js app

9 |
10 |

Latest News

11 |
    12 |
  • 13 | Gaza News 14 |
  • 15 |
  • 16 | Westbank News 17 |
  • 18 |
19 |
20 |
21 | ); 22 | } 23 | 24 | export default Home; -------------------------------------------------------------------------------- /4_news_app/.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.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | # .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /4_news_app/app/(main)/add-news/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './add-article.module.css'; 3 | import AddArticleForm from '@/components/add-article/AddArticle'; 4 | import { Metadata } from 'next'; 5 | 6 | export const metadata: Metadata = { 7 | title: 'Add new Article!', 8 | description: 'GSG News website, Add new Article!' 9 | } 10 | 11 | const Page = () => { 12 | return ( 13 |
14 |

Add News Page

15 |

Please fill all the required news data

16 | 17 |
18 | ) 19 | } 20 | 21 | export default Page; -------------------------------------------------------------------------------- /memory-card-game/src/screens/game.screen.tsx: -------------------------------------------------------------------------------- 1 | import './screens.css'; 2 | import CardList from '../components/card-list/card-list'; 3 | import Congrats from '../components/congrats/congrats'; 4 | 5 | import StatusBar from '../components/status-bar/status-bar'; 6 | import useGameLogic from '../hooks/game-logic.hook'; 7 | 8 | const GameScreen = () => { 9 | const { state, dispatch, gameMode } = useGameLogic(); 10 | 11 | return ( 12 |
13 | 14 | 18 | {gameMode.finished && } 19 |
20 | ) 21 | } 22 | 23 | export default GameScreen; -------------------------------------------------------------------------------- /4_news_app/app/(auth)/user/login/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from "./login.module.css"; 3 | import LoginForm from '@/components/auth/login-form/LoginForm'; 4 | 5 | interface IProps { 6 | searchParams: Promise<{ msg: string }>; 7 | } 8 | 9 | const Page = async (props: IProps) => { 10 | const { msg } = (await props.searchParams); 11 | 12 | return ( 13 |
14 |

{msg}

15 |

Welcome Back

16 |

Please enter your credentials to login

17 | 18 |
19 | ) 20 | } 21 | 22 | export default Page; -------------------------------------------------------------------------------- /4_news_app/components/latest-news/latest-news.module.css: -------------------------------------------------------------------------------- 1 | .latestNews { 2 | background: #FAFAFBFF; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | padding: 50px 0; 7 | border-bottom: 1px solid #eee; 8 | } 9 | 10 | .latestNews>h2 { 11 | font-family: var(--font-roboto); 12 | font-size: 24px; 13 | font-weight: 700; 14 | color: #000; 15 | margin: 10px 0; 16 | } 17 | 18 | .latestNews>h3 { 19 | font-family: var(--font-roboto); 20 | font-size: 20px; 21 | font-weight: 700; 22 | color: #999; 23 | margin: 0; 24 | margin-bottom: 20px; 25 | } 26 | 27 | .items { 28 | display: flex; 29 | justify-content: center; 30 | gap: 20px; 31 | flex-wrap: wrap; 32 | } -------------------------------------------------------------------------------- /1_Students App/src/components/student/student.css: -------------------------------------------------------------------------------- 1 | .std-wrapper { 2 | background-color: #ffffff; 3 | border-radius: 20px; 4 | box-shadow: 4px 4px 9px -7px #b4b4b4; 5 | padding: 16px; 6 | } 7 | 8 | .std-wrapper ul.courses-list { 9 | list-style-type: none; 10 | display: flex; 11 | gap: 10px; 12 | flex-wrap: wrap; 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | .std-wrapper .data-field { 18 | text-align: left; 19 | display: flex; 20 | flex-direction: row; 21 | column-gap: 20px; 22 | } 23 | 24 | .std-wrapper .data-field b { 25 | color: #5b5b5b; 26 | } 27 | 28 | .std-wrapper .absents { 29 | display: flex; 30 | justify-content: flex-end; 31 | column-gap: 10px; 32 | margin-top: 20px; 33 | } -------------------------------------------------------------------------------- /2_Todo App/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /testing-ui/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /4_news_app/components/header/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './header.module.css'; 3 | import logo from '@/public/logo.png'; 4 | import NavLink from './NavLink'; 5 | 6 | const Header = () => { 7 | return ( 8 |
9 |
10 | logo 11 |

Quick News

12 |
13 | 19 |
20 | ) 21 | } 22 | 23 | export default Header -------------------------------------------------------------------------------- /3_next-example/app/reach/layout.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import React from 'react'; 3 | import classes from './layout.module.css'; 4 | 5 | interface IProps { 6 | children: React.ReactNode; 7 | } 8 | 9 | const ReachUSLayout = (props: IProps) => { 10 | return ( 11 |
12 | 22 |
23 | {props.children} 24 |
25 |
26 | ) 27 | } 28 | 29 | export default ReachUSLayout; -------------------------------------------------------------------------------- /memory-card-game/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /1_Students App/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /4_news_app/components/hero/hero.module.css: -------------------------------------------------------------------------------- 1 | .hero { 2 | width: 100%; 3 | height: 350px; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | flex-direction: column; 8 | row-gap: 30px; 9 | } 10 | 11 | .hero h2 { 12 | font-family: var(--font-roboto); 13 | font-size: 64px; 14 | line-height: 84px; 15 | font-weight: 700; 16 | color: #171A1FFF; 17 | display: flex; 18 | justify-content: center; 19 | margin: 0; 20 | } 21 | 22 | .hero span { 23 | font-family: var(--font-mulish); 24 | font-size: 20px; 25 | line-height: 30px; 26 | font-weight: 400; 27 | color: #1D2128FF; 28 | } 29 | 30 | .hero .actions { 31 | display: flex; 32 | column-gap: 30px; 33 | } -------------------------------------------------------------------------------- /3_next-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /4_news_app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "NodeNext", 11 | "moduleResolution": "nodenext", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /3_next-example/app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | :root { 4 | --background: #ffffff; 5 | --foreground: #171717; 6 | } 7 | 8 | @media (prefers-color-scheme: dark) { 9 | :root { 10 | --background: #0a0a0a; 11 | --foreground: #ededed; 12 | } 13 | } 14 | 15 | html, 16 | body { 17 | max-width: 100vw; 18 | overflow-x: hidden; 19 | } 20 | 21 | body { 22 | color: var(--foreground); 23 | background: var(--background); 24 | font-family: Arial, Helvetica, sans-serif; 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | } 28 | 29 | a { 30 | color: inherit; 31 | text-decoration: none; 32 | } 33 | 34 | @media (prefers-color-scheme: dark) { 35 | html { 36 | color-scheme: dark; 37 | } 38 | } -------------------------------------------------------------------------------- /memory-card-game/src/utils/game.util.ts: -------------------------------------------------------------------------------- 1 | import { ELevels, ICard } from "../types/@types"; 2 | 3 | const EMPTY_CARD: ICard = { 4 | id: 0, image: '', visible: false, revealed: false 5 | } 6 | 7 | export const createGameBoard = (level: ELevels): ICard[] => { 8 | let cards: ICard[] = Array.from({ length: level * level }, 9 | (_, index) => (index % 2 === 0) ? { ...EMPTY_CARD, id: index } : { ...EMPTY_CARD, id: index - 1, image: '' }) 10 | .sort(() => Math.random() - 0.5); 11 | 12 | // fill cards images 13 | cards = cards.map(c => ({ ...c, image: `https://api.clipart.com/img/previews/education-${c.id + 1}.jpg` })); 14 | return cards; 15 | } 16 | 17 | export const checkedFinished = (cards: ICard[]) => { 18 | return cards.every(c => c.revealed); 19 | } -------------------------------------------------------------------------------- /memory-card-game/src/components/congrats/congrats.css: -------------------------------------------------------------------------------- 1 | .congrats { 2 | display: flex; 3 | position: absolute; 4 | font-family: 'Courier New', Courier, monospace; 5 | z-index: 5; 6 | color: #fff34b; 7 | background-color: rgb(5 19 40); 8 | font-size: 22px; 9 | align-content: center; 10 | align-items: center; 11 | flex-direction: column; 12 | row-gap: 30px; 13 | padding: 70px; 14 | max-width: 75vw; 15 | 16 | color: #fff; 17 | text-shadow: 0 0 7px #000000, 18 | 0 0 10px #8c4a4a, 19 | 0 0 21px #a8a7de, 20 | 0 0 42px #bc13fe, 21 | 0 0 82px #bc13fe, 22 | 0 0 92px #bc13fe, 23 | 0 0 102px #bc13fe, 24 | 0 0 151px #bc13fe; 25 | box-shadow: 0px 0px 18px 7px #1c2866; 26 | } -------------------------------------------------------------------------------- /4_news_app/components/category/Category.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './category.module.css'; 3 | import Image from 'next/image'; 4 | import Link from 'next/link'; 5 | 6 | interface IProps { 7 | data: News.ICategory; 8 | } 9 | 10 | const Category = (props: IProps) => { 11 | return ( 12 |
13 | 14 |
15 | Cat Image 16 |
17 |

{props.data.title}

18 |

{props.data.subtitle}

19 | 20 |
21 | ) 22 | } 23 | 24 | export default Category; -------------------------------------------------------------------------------- /2_Todo App/src/components/todo-list/todo-list.component.tsx: -------------------------------------------------------------------------------- 1 | import TodoItem from "../todo-item/todo-item.component"; 2 | import { ITodoItem } from "../types"; 3 | import './todo-list.css'; 4 | 5 | interface IProps { 6 | items: ITodoItem[]; 7 | onToggle: (e: React.ChangeEvent) => void; 8 | onDelete: (index: number) => void; 9 | } 10 | 11 | const TodoList = (props: IProps) => { 12 | return ( 13 |
14 | { 15 | props.items.map((item, index) => ( 16 | props.onDelete(index)} 21 | /> 22 | )) 23 | } 24 |
25 | ) 26 | } 27 | 28 | export default TodoList; -------------------------------------------------------------------------------- /2_Todo App/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /testing-ui/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /memory-card-game/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /testing-ui/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /memory-card-game/src/components/score-list/score-list.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { IScore } from '../../types/@types'; 3 | import './score-list.css'; 4 | 5 | interface IProps { 6 | scores: IScore[]; 7 | } 8 | const ScoreList = (props: IProps) => { 9 | return ( 10 |
    11 | { 12 | props.scores.map((score, index) => ( 13 |
  • 14 | Name: {score.playerName} 15 | | Level: {Array.from({ length: score.level / 2 }).map(_ => '⭐')} 16 | | Time: {score.finishTime} 17 | | Wrong Moves: {score.wrongMoves} 18 |
  • 19 | ) 20 | ) 21 | } 22 |
23 | ) 24 | } 25 | 26 | export default ScoreList; -------------------------------------------------------------------------------- /testing-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testing-ui", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^19.0.0", 14 | "react-dom": "^19.0.0" 15 | }, 16 | "devDependencies": { 17 | "@eslint/js": "^9.21.0", 18 | "@types/react": "^19.0.10", 19 | "@types/react-dom": "^19.0.4", 20 | "@vitejs/plugin-react": "^4.3.4", 21 | "eslint": "^9.21.0", 22 | "eslint-plugin-react-hooks": "^5.1.0", 23 | "eslint-plugin-react-refresh": "^0.4.19", 24 | "globals": "^15.15.0", 25 | "typescript": "~5.7.2", 26 | "typescript-eslint": "^8.24.1", 27 | "vite": "^6.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /2_Todo App/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@phosphor-icons/react": "^2.1.7", 14 | "react": "^18.3.1", 15 | "react-dom": "^18.3.1" 16 | }, 17 | "devDependencies": { 18 | "@eslint/js": "^9.17.0", 19 | "@types/react": "^18.3.18", 20 | "@types/react-dom": "^18.3.5", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "eslint": "^9.17.0", 23 | "eslint-plugin-react-hooks": "^5.0.0", 24 | "eslint-plugin-react-refresh": "^0.4.16", 25 | "globals": "^15.14.0", 26 | "typescript": "~5.6.2", 27 | "typescript-eslint": "^8.18.2", 28 | "vite": "^6.0.5" 29 | } 30 | } -------------------------------------------------------------------------------- /1_Students App/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "first-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1", 15 | "react-router-dom": "^7.1.1" 16 | }, 17 | "devDependencies": { 18 | "@eslint/js": "^9.15.0", 19 | "@types/react": "^18.3.12", 20 | "@types/react-dom": "^18.3.1", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "eslint": "^9.15.0", 23 | "eslint-plugin-react-hooks": "^5.0.0", 24 | "eslint-plugin-react-refresh": "^0.4.14", 25 | "globals": "^15.12.0", 26 | "typescript": "~5.6.2", 27 | "typescript-eslint": "^8.15.0", 28 | "vite": "^6.0.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /3_next-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@ant-design/icons": "^5.6.1", 13 | "@ant-design/nextjs-registry": "^1.0.2", 14 | "@tailwindcss/postcss": "^4.0.3", 15 | "antd": "^5.23.4", 16 | "next": "15.1.6", 17 | "postcss": "^8.5.1", 18 | "react": "^19.0.0", 19 | "react-dom": "^19.0.0", 20 | "tailwindcss": "^4.0.3" 21 | }, 22 | "devDependencies": { 23 | "@eslint/eslintrc": "^3", 24 | "@types/node": "^20", 25 | "@types/react": "^19", 26 | "@types/react-dom": "^19", 27 | "eslint": "^9", 28 | "eslint-config-next": "15.1.6", 29 | "typescript": "^5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /memory-card-game/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memory-card-game", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1", 15 | "react-router-dom": "^7.1.3" 16 | }, 17 | "devDependencies": { 18 | "@eslint/js": "^9.17.0", 19 | "@types/react": "^18.3.18", 20 | "@types/react-dom": "^18.3.5", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "eslint": "^9.17.0", 23 | "eslint-plugin-react-hooks": "^5.0.0", 24 | "eslint-plugin-react-refresh": "^0.4.16", 25 | "globals": "^15.14.0", 26 | "typescript": "~5.6.2", 27 | "typescript-eslint": "^8.18.2", 28 | "vite": "^6.0.5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /testing-ui/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /memory-card-game/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /4_news_app/components/category/category.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | position: relative; 3 | width: 330px; 4 | margin: 5px; 5 | background: #FFF; 6 | border-radius: 2px; 7 | box-shadow: 0 0 5px 0px #171a1f7d, 0 0 2px #171a1f38; 8 | display: flex; 9 | flex-direction: column; 10 | row-gap: 10px; 11 | padding-bottom: 10px; 12 | } 13 | 14 | .wrapper a { 15 | text-decoration: none; 16 | } 17 | 18 | .title { 19 | font-family: var(--font-mulish); 20 | font-size: 18px; 21 | font-weight: 700; 22 | color: #9095A0FF; 23 | margin: 0; 24 | padding: 0 10px; 25 | } 26 | 27 | .latest { 28 | font-family: var(--font-mulish); 29 | font-size: 16px; 30 | font-weight: 400; 31 | color: #9095A0FF; 32 | margin: 0; 33 | padding: 0 10px; 34 | } 35 | 36 | .banner { 37 | position: relative; 38 | width: 100%; 39 | height: 160px; 40 | } 41 | 42 | .banner img { 43 | object-fit: cover; 44 | } -------------------------------------------------------------------------------- /memory-card-game/src/components/card/card.tsx: -------------------------------------------------------------------------------- 1 | import { Action } from '../../providers/reducer'; 2 | import { ICard } from '../../types/@types'; 3 | import './card.css'; 4 | 5 | interface IProps { 6 | data: ICard; 7 | index: number; 8 | dispatch: React.Dispatch; 9 | } 10 | 11 | const Card = (props: IProps) => { 12 | const handleFlip = () => { 13 | props.dispatch({ type: 'flip-card', payload: { id: props.data.id, index: props.index } }); 14 | } 15 | 16 | return ( 17 |
21 |
22 |
23 |
27 |
28 |
29 | ) 30 | } 31 | 32 | export default Card; -------------------------------------------------------------------------------- /2_Todo App/src/hooks/local-storage.hook.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | const useLocalStorage = (state: any, storageKey: string) => { 4 | const [storedData, setStoredData] = useState(); 5 | 6 | useEffect(() => { 7 | // Read the data on the first render 8 | const strData = localStorage.getItem(storageKey); 9 | try { 10 | if (strData !== null) { 11 | setStoredData(JSON.parse(strData)); 12 | } else { 13 | setStoredData(null); 14 | } 15 | } catch { 16 | setStoredData(strData); 17 | } 18 | }, []); 19 | 20 | useEffect(() => { 21 | if (typeof (state) === 'object') { 22 | localStorage.setItem(storageKey, JSON.stringify(state)); 23 | } else { 24 | localStorage.setItem(storageKey, state.toString()); 25 | } 26 | }, [state, storageKey]); 27 | 28 | return { storedData }; 29 | } 30 | 31 | export default useLocalStorage; -------------------------------------------------------------------------------- /memory-card-game/src/components/card-list/card-list.tsx: -------------------------------------------------------------------------------- 1 | import { ICard } from '../../types/@types'; 2 | import Card from '../card/card'; 3 | import './card-list.css'; 4 | import { Action } from '../../providers/reducer'; 5 | import { useContext } from 'react'; 6 | import { GameModeContext } from '../../providers/modeProvider'; 7 | 8 | interface IProps { 9 | cards: ICard[]; 10 | dispatch: React.Dispatch; 11 | } 12 | 13 | const CardList = (props: IProps) => { 14 | const { gameMode } = useContext(GameModeContext); 15 | 16 | return ( 17 |
18 | { 19 | props.cards.map((card, index) => ( 20 | 26 | )) 27 | } 28 |
29 | ) 30 | } 31 | 32 | export default CardList; -------------------------------------------------------------------------------- /2_Todo App/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | '@typescript-eslint/no-explicit-any': 'warn', 23 | 'react-refresh/only-export-components': [ 24 | 'warn', 25 | { allowConstantExport: true }, 26 | ], 27 | }, 28 | }, 29 | ) 30 | -------------------------------------------------------------------------------- /2_Todo App/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import App from './App.tsx' 3 | import { createContext, useEffect, useState } from 'react' 4 | 5 | 6 | interface IContextState { 7 | theme: string, 8 | setTheme: React.Dispatch> 9 | } 10 | 11 | const storedTheme = localStorage.getItem('theme'); 12 | const DEFAULT_THEME = storedTheme || 'light'; 13 | 14 | export const ThemeContext = createContext({ theme: DEFAULT_THEME, setTheme: () => { } }); 15 | 16 | const WrapperComponent = () => { 17 | const [theme, setTheme] = useState(DEFAULT_THEME); 18 | 19 | useEffect(() => { 20 | localStorage.setItem('theme', theme); 21 | }, [theme]); 22 | 23 | return ( 24 | 25 | 26 | 27 | ) 28 | } 29 | 30 | createRoot(document.getElementById('root')!).render( 31 | 32 | ); -------------------------------------------------------------------------------- /4_news_app/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | images: { 5 | remotePatterns: [ // This will allow all external hosts/domains 6 | { 7 | hostname: "**", 8 | }, 9 | ], 10 | domains: ['loremflickr.com'] // This to allow specific domains only 11 | }, 12 | async headers() { 13 | return [ 14 | { 15 | source: "/api/:path*", 16 | headers: [ 17 | { 18 | key: "Access-Control-Allow-Origin", 19 | value: "*", 20 | }, 21 | { 22 | key: "Access-Control-Allow-Methods", 23 | value: "GET, POST, PUT, DELETE, OPTIONS", 24 | }, 25 | { 26 | key: "Access-Control-Allow-Headers", 27 | value: "Content-Type, Authorization", 28 | } 29 | ], 30 | }, 31 | ]; 32 | }, 33 | }; 34 | 35 | export default nextConfig; 36 | -------------------------------------------------------------------------------- /3_next-example/app/reach/about/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const About = () => { 4 | return ( 5 |
6 |

About App

7 |

8 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Ullam assumenda fugit, ut alias sunt reiciendis facere voluptates laboriosam possimus pariatur, delectus commodi quibusdam corrupti! Quam porro iste dolore temporibus voluptatum. 9 | Magni ipsum veniam temporibus sit modi necessitatibus atque, dolore commodi veritatis cupiditate doloremque accusantium. Praesentium commodi tempore et animi, temporibus eligendi natus sequi, ipsa veniam, maxime voluptas aperiam asperiores quibusdam? 10 | Porro magni amet maxime recusandae explicabo sed. Ducimus quos cupiditate, animi et, alias accusamus inventore minima soluta molestias cumque repellat nobis nam. Debitis nisi aliquam nulla sit? Quo, repudiandae atque? 11 |

12 |
13 | ) 14 | } 15 | 16 | export default About -------------------------------------------------------------------------------- /3_next-example/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist, Geist_Mono } from "next/font/google"; 3 | import "./globals.css"; 4 | import MainHeader from "@/components/main-header/MainHeader"; 5 | import { AntdRegistry } from '@ant-design/nextjs-registry'; 6 | 7 | const geistSans = Geist({ 8 | variable: "--font-geist-sans", 9 | subsets: ["latin"], 10 | }); 11 | 12 | const geistMono = Geist_Mono({ 13 | variable: "--font-geist-mono", 14 | subsets: ["latin"], 15 | }); 16 | 17 | export const metadata: Metadata = { 18 | title: "Create Next App", 19 | description: "Generated by create next app", 20 | }; 21 | 22 | export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { 23 | return ( 24 | 25 | 26 | 27 | {children} 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /4_news_app/constants/data.ts: -------------------------------------------------------------------------------- 1 | const CATEGORIES: News.ICategory[] = [ 2 | { 3 | title: 'finance', 4 | imageURL: '/cats/finance.webp', 5 | subtitle: '' 6 | }, 7 | { 8 | title: 'gaza', 9 | imageURL: '/cats/gaza.webp', 10 | subtitle: '' 11 | }, 12 | { 13 | title: 'global', 14 | imageURL: '/cats/global.webp', 15 | subtitle: '' 16 | }, 17 | { 18 | title: 'palestine', 19 | imageURL: '/cats/palestine.webp', 20 | subtitle: '' 21 | }, 22 | { 23 | title: 'sports', 24 | imageURL: '/cats/sports.webp', 25 | subtitle: '' 26 | }, 27 | { 28 | title: 'weather', 29 | imageURL: '/cats/weather.webp', 30 | subtitle: '' 31 | }, 32 | { 33 | title: 'westbank', 34 | imageURL: '/cats/westbank.webp', 35 | subtitle: '' 36 | } 37 | ]; 38 | 39 | const ALLOWED_CATEGORIES = [ 40 | 'global', 41 | 'palestine', 42 | 'gaza', 43 | 'finance', 44 | 'westbank', 45 | 'weather', 46 | 'sports', 47 | ] 48 | 49 | export { CATEGORIES, ALLOWED_CATEGORIES }; -------------------------------------------------------------------------------- /4_news_app/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /3_next-example/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /1_Students App/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | '@typescript-eslint/no-unused-vars': 'warn', 23 | '@typescript-eslint/no-empty-object-type': 'warn', 24 | '@typescript-eslint/no-explicit-any': 'warn', 25 | 'react-refresh/only-export-components': [ 26 | 'warn', 27 | { allowConstantExport: true }, 28 | ], 29 | }, 30 | }, 31 | ) 32 | -------------------------------------------------------------------------------- /1_Students App/src/components/common/guarded-route/guarded-route.component.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { AuthContext } from '../../../providers/authProvider'; 3 | import { Link } from 'react-router-dom'; 4 | import { Role } from '../../../@types'; 5 | 6 | interface IProps { 7 | children: React.ReactNode; 8 | roles: Role[]; 9 | } 10 | 11 | const Guarded = (props: IProps) => { 12 | const { user, loading } = useContext(AuthContext); 13 | 14 | if (loading) { 15 | return null; 16 | } 17 | 18 | if (user === null) { // User is not logged in 19 | return ( 20 |
21 |

You must be logged in to see this screen!

22 | Login in here 23 |
24 | ); 25 | } else if (!props.roles.includes(user.role)) { // User doesn't have permission 26 | return ( 27 |
28 |

You don't have sufficient permissions to see this screen!

29 |
30 | ); 31 | } 32 | 33 | return props.children; 34 | } 35 | 36 | export default Guarded; -------------------------------------------------------------------------------- /4_news_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4_news_app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@phosphor-icons/react": "^2.1.7", 13 | "bcryptjs": "^3.0.2", 14 | "better-sqlite3": "^11.8.1", 15 | "classnames": "^2.5.1", 16 | "jose": "^6.0.10", 17 | "jsonwebtoken": "^9.0.2", 18 | "next": "15.1.6", 19 | "react": "^19.0.0", 20 | "react-dom": "^19.0.0", 21 | "react-toastify": "^11.0.5", 22 | "slugify": "^1.6.6", 23 | "xss": "^1.0.15" 24 | }, 25 | "devDependencies": { 26 | "@eslint/eslintrc": "^3", 27 | "@types/bcryptjs": "^3.0.0", 28 | "@types/better-sqlite3": "^7.6.12", 29 | "@types/jsonwebtoken": "^9.0.9", 30 | "@types/node": "^20", 31 | "@types/react": "^19", 32 | "@types/react-dom": "^19", 33 | "eslint": "^9", 34 | "eslint-config-next": "15.1.6", 35 | "typescript": "^5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /4_news_app/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace News { 2 | export interface IResponseNewsItem { 3 | article_id: string; 4 | description: string; 5 | title: string; 6 | image_url: string; 7 | } 8 | 9 | export interface IResponse { 10 | status: string; 11 | totalResults: string; 12 | results: IResponseNewsItem[]; 13 | } 14 | 15 | export interface Item { 16 | id: string; 17 | title: string; 18 | img: string | null; 19 | content: string; 20 | } 21 | 22 | export interface ICategory { 23 | title: string; 24 | subtitle: string; 25 | imageURL: string; 26 | } 27 | 28 | export interface Item_ { 29 | id?: string; 30 | title: string; 31 | slug: string; 32 | image: string; 33 | summary: string; 34 | content: string; 35 | author: string; 36 | author_email: string; 37 | date: number; 38 | category: string; 39 | } 40 | 41 | export interface IUser { 42 | email: string; 43 | password?: string; 44 | role: string; 45 | displayName: string; 46 | } 47 | } -------------------------------------------------------------------------------- /memory-card-game/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter, RouteObject, RouterProvider } from 'react-router-dom'; 2 | import './App.css' 3 | import GameScreen from './screens/game.screen'; 4 | import LevelsScreen from './screens/levels.screen'; 5 | import ScoreBoardScreen from './screens/score-board.screen'; 6 | import NotFound from './screens/not-found.screen'; 7 | import { GameModeProvider } from './providers/modeProvider'; 8 | 9 | function App() { 10 | const routes: RouteObject[] = [ 11 | { 12 | path: '/', 13 | element: 14 | }, 15 | { 16 | path: '/game', 17 | element: 18 | }, 19 | { 20 | path: '/score-board', 21 | element: 22 | }, 23 | { 24 | path: '*', 25 | element: 26 | } 27 | ]; 28 | 29 | const browserRouter = createBrowserRouter(routes); 30 | 31 | return ( 32 | 33 | 34 | 35 | ) 36 | } 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /4_news_app/components/latest-news/item/Item.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classes from './item.module.css'; 3 | import Image from 'next/image'; 4 | 5 | interface IProps { 6 | isHighlighted: boolean; 7 | data: News.Item; 8 | } 9 | 10 | const Item = (props: IProps) => { 11 | const item = props.data; 12 | 13 | return ( 14 |
15 |

{item.title?.substring(0, 95)}

16 |
17 | { 18 | new-img 24 | } 25 |
26 |

{item.content?.substring(0, 150)}...

27 |
28 | ) 29 | } 30 | 31 | export default Item; -------------------------------------------------------------------------------- /2_Todo App/src/components/dashboard/dashboard-component.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useMemo } from 'react'; 2 | import { ITodoItem } from '../types'; 3 | import './dashboard.css'; 4 | import { ThemeContext } from '../../main'; 5 | 6 | interface IProps { 7 | items: ITodoItem[]; 8 | } 9 | 10 | const Dashboard = (props: IProps) => { 11 | const { theme } = useContext(ThemeContext) 12 | const urgentCount = useMemo(() => { 13 | return props.items.filter(item => item.isUrgent).length; 14 | }, [props.items]); 15 | 16 | const completedCount = useMemo(() => { 17 | return props.items.filter(item => item.isDone).length; 18 | }, [props.items]); 19 | 20 | return ( 21 |
22 |
23 | {props.items.length} 24 | Created Tasks 25 |
26 |
27 | {urgentCount} 28 | Urgent Tasks 29 |
30 |
31 | {completedCount} 32 | Completed Tasks 33 |
34 |
35 | ) 36 | } 37 | 38 | export default Dashboard -------------------------------------------------------------------------------- /4_news_app/components/article-item/ArticleItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | import Image from 'next/image'; 4 | 5 | interface IProps { 6 | item: News.Item_; 7 | } 8 | 9 | const ArticleItem = (props: IProps) => { 10 | const { item } = props; 11 | 12 | return ( 13 | 17 |

{item.title?.substring(0, 95)}

18 |
19 | { 20 | new-img 26 | } 27 |
28 |

{item.summary}

29 | 30 | ) 31 | } 32 | 33 | export default ArticleItem; -------------------------------------------------------------------------------- /4_news_app/app/api/news/route.ts: -------------------------------------------------------------------------------- 1 | import { ALLOWED_CATEGORIES } from "@/constants/data"; 2 | import { getNewsByCategory } from "@/services/news.service"; 3 | import { NextRequest, NextResponse } from "next/server"; 4 | 5 | const GET = async (request: NextRequest) => { 6 | const params = request.nextUrl.searchParams; 7 | const category = params.get('category') || 'global'; 8 | 9 | if (!ALLOWED_CATEGORIES.includes(category)) { 10 | return NextResponse.json(null, { status: 400, statusText: "Unknown category" }); 11 | } 12 | 13 | const news = getNewsByCategory(category); 14 | return NextResponse.json( 15 | { results: news }, 16 | { status: 200 } 17 | ); 18 | } 19 | 20 | const POST = async (request: NextRequest) => { 21 | const body = await request.json(); 22 | console.log(body); 23 | return NextResponse.json({ 24 | msg: "Item Added" 25 | }, { 26 | status: 201, 27 | // headers: { 28 | // 'Access-Control-Allow-Origin': '*', 29 | // 'Access-Control-Allow-Methods': 'GET', 30 | // 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 31 | // }, 32 | }); 33 | } 34 | 35 | export { 36 | GET, POST 37 | } -------------------------------------------------------------------------------- /memory-card-game/src/providers/modeProvider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from "react"; 2 | import { ELevels } from "../types/@types"; 3 | 4 | export interface IGameMode { 5 | playerName: string; 6 | level: ELevels; 7 | finished: boolean; 8 | time: number; 9 | wrongMoves: number; 10 | } 11 | 12 | export interface IGameModeContext { 13 | gameMode: IGameMode; 14 | setGameMode: React.Dispatch>; 15 | resetGame: () => void; 16 | } 17 | 18 | const INIT_STATE: IGameMode = { playerName: '', level: ELevels.EASY, finished: false, time: 0, wrongMoves: 0 }; 19 | 20 | export const GameModeContext = createContext( 21 | { gameMode: INIT_STATE, setGameMode: () => { }, resetGame: () => { } } 22 | ); 23 | 24 | export const GameModeProvider = (props: { children: React.ReactNode }) => { 25 | const [gameMode, setGameMode] = useState(INIT_STATE); 26 | 27 | const resetGame = () => { 28 | setGameMode(old => ({ ...INIT_STATE, level: old.level, playerName: old.playerName })); 29 | } 30 | 31 | return {props.children} 32 | } -------------------------------------------------------------------------------- /4_news_app/components/latest-news/LatestNews.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React, { useEffect, useState } from 'react'; 3 | import classes from './latest-news.module.css'; 4 | import Item from './item/Item'; 5 | 6 | interface IProps { 7 | subTitle?: string; 8 | newsList: News.Item[]; 9 | } 10 | 11 | const LatestNews = (props: IProps) => { 12 | const [highlightedIndex, setHighlightedIndex] = useState(0); 13 | 14 | useEffect(() => { 15 | const sliderInt = setInterval(() => { 16 | setHighlightedIndex(old => (old + 1) % 3); 17 | }, 3000); 18 | 19 | return () => { 20 | clearInterval(sliderInt); 21 | } 22 | }, []); 23 | 24 | return ( 25 |
26 |

Latest News Articles

27 | {props.subTitle &&

{props.subTitle}

} 28 |
29 | { 30 | props.newsList.map((data, index) => ( 31 | 36 | )) 37 | } 38 |
39 |
40 | ) 41 | } 42 | 43 | export default LatestNews; -------------------------------------------------------------------------------- /4_news_app/utils/auth.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import { compareSync, hashSync } from "bcryptjs"; 4 | import * as jose from 'jose'; 5 | const JWT_SECRET = process.env.JWT_SECRET || ''; 6 | 7 | const comparePassword = (password: string, hashedPassword: string): boolean => { 8 | return compareSync(password, hashedPassword); 9 | } 10 | 11 | const hashPassword = (password: string): string => { 12 | return hashSync(password); 13 | } 14 | 15 | const generateToken = async (user: News.IUser) => { 16 | const token = await new jose.SignJWT({ email: user.email, role: user.role, displayName: user.displayName }) 17 | .setExpirationTime('1w') 18 | .setProtectedHeader({ alg: 'HS256' }) 19 | .sign(new TextEncoder().encode(JWT_SECRET)); 20 | 21 | return token; 22 | } 23 | 24 | const verifyToken = async (token: string): Promise => { 25 | try { 26 | const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(JWT_SECRET)); 27 | return payload as unknown as News.IUser; 28 | } catch (err) { 29 | console.log(err); 30 | return null; 31 | } 32 | } 33 | 34 | export { 35 | comparePassword, 36 | hashPassword, 37 | generateToken, 38 | verifyToken 39 | } -------------------------------------------------------------------------------- /1_Students App/src/providers/authProvider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useLayoutEffect, useState } from "react"; 2 | import { IUserData } from "../@types"; 3 | import useLocalStorage from "../hooks/local-storage.hook"; 4 | 5 | export interface IAuthContext { 6 | user: IUserData | null; 7 | loading: boolean; 8 | login: (data: IUserData) => void; 9 | logout: () => void; 10 | } 11 | 12 | export const AuthContext = createContext({ user: null, login: () => { }, logout: () => { }, loading: true }); 13 | 14 | export const AuthProvider = (props: { children: React.ReactNode }) => { 15 | const [user, setUser] = useState(null); 16 | const { storedData, loading } = useLocalStorage(user, 'auth-user'); 17 | 18 | useLayoutEffect(() => { 19 | if (!loading) { 20 | setUser(storedData); 21 | } 22 | }, [storedData, loading]); 23 | 24 | const login = (data: IUserData) => { 25 | if (data.userName.length >= 3) { 26 | setUser(data); 27 | } else { 28 | setUser(null); 29 | } 30 | } 31 | 32 | const logout = () => { 33 | setUser(null); 34 | } 35 | 36 | const data = { user, loading, login, logout }; 37 | 38 | return {props.children}; 39 | }; -------------------------------------------------------------------------------- /2_Todo App/src/components/todo-item/todo-item.component.tsx: -------------------------------------------------------------------------------- 1 | import { Trash } from '@phosphor-icons/react'; 2 | import './todo-item.css' 3 | import { ITodoItem } from '../types'; 4 | import { useContext } from 'react'; 5 | import { ThemeContext } from '../../main'; 6 | 7 | interface IProps { 8 | data: ITodoItem 9 | onToggle: (e: React.ChangeEvent) => void 10 | onDelete: () => void 11 | }; 12 | 13 | const TodoItem = ({ data, onToggle, onDelete }: IProps) => { 14 | const { theme } = useContext(ThemeContext); 15 | return ( 16 |
17 | 18 |
19 | 26 | 27 |
28 | {data.title} 29 |
30 | 31 |
32 | ) 33 | } 34 | 35 | export default TodoItem; -------------------------------------------------------------------------------- /4_news_app/app/(main)/news-list/[[...slug]]/page.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | import classes from './news-list.module.css'; 3 | import NewsList from './news-list'; 4 | import { Metadata } from 'next'; 5 | 6 | interface IProps { 7 | params: Promise<{ slug: string[] }> 8 | } 9 | 10 | export const generateMetadata = async (props: IProps): Promise => { 11 | const { slug } = await props.params; 12 | const [category] = slug; 13 | 14 | return { 15 | title: `${category.toUpperCase()} News` 16 | } 17 | } 18 | 19 | const NewsListPage = async (props: IProps) => { 20 | const { slug } = await props.params; 21 | 22 | if (!slug?.length) { 23 | return ( 24 |
25 |

You reached this page without selecting a country or category!

26 |
27 | ); 28 | } 29 | 30 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 31 | const [category, _year] = slug; 32 | 33 | return ( 34 |
35 |

{category} News

36 | }> 37 | 38 | 39 |
40 | ) 41 | } 42 | 43 | export default NewsListPage; 44 | -------------------------------------------------------------------------------- /1_Students App/src/providers/stateProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useLayoutEffect, useReducer } from 'react' 2 | import { stateReducer, State, Action } from '../state/reducer'; 3 | import useLocalStorage from '../hooks/local-storage.hook'; 4 | import { IStudent } from '../@types'; 5 | 6 | interface IProps { 7 | children: React.ReactNode; 8 | } 9 | 10 | interface IStateContext { 11 | state: State; 12 | loading: boolean; 13 | dispatch: React.Dispatch; 14 | } 15 | 16 | const INTI_STATE = { state: { totalAbsents: 0, studentsList: [] }, loading: true, dispatch: () => { } }; 17 | 18 | export const StateContext = createContext(INTI_STATE); 19 | 20 | const StateProvider = (props: IProps) => { 21 | const [state, dispatch] = useReducer(stateReducer, { studentsList: [], totalAbsents: 0 }); 22 | const { storedData, loading } = useLocalStorage(state.studentsList, 'students-list'); 23 | 24 | useLayoutEffect(() => { 25 | if (!loading) { 26 | const stdList: IStudent[] = storedData || []; 27 | dispatch({ type: "INIT", payload: stdList }); 28 | } 29 | }, [loading, storedData]); 30 | 31 | return ( 32 | {props.children} 33 | ) 34 | } 35 | 36 | export default StateProvider -------------------------------------------------------------------------------- /2_Todo App/src/state/reducer.ts: -------------------------------------------------------------------------------- 1 | import { ITodoItem } from "../components/types"; 2 | 3 | interface IState { 4 | todos: ITodoItem[]; 5 | userName: string; 6 | } 7 | 8 | // interface IAction { 9 | // type: string; 10 | // payload: any; 11 | // } 12 | 13 | type Action = { type: 'INIT_TODOS', payload: ITodoItem[] } 14 | | { type: 'ADD_TODO', payload: ITodoItem } 15 | | { type: 'REMOVE_TODO', payload: number } 16 | | { type: 'TOGGLE_TODO', payload: number } 17 | 18 | const reducer = (state: IState, action: Action): IState => { 19 | switch (action.type) { 20 | case 'INIT_TODOS': { 21 | if (state.todos.length === 0) { 22 | return { ...state, todos: action.payload } 23 | } 24 | return state; 25 | } 26 | case 'ADD_TODO': { 27 | const newTodo = action.payload; 28 | newTodo.id = Date.now(); 29 | return { ...state, todos: [...state.todos, newTodo] } 30 | } 31 | case 'REMOVE_TODO': 32 | return { ...state, todos: state.todos.filter(item => item.id !== action.payload) } 33 | case 'TOGGLE_TODO': { 34 | return { ...state, todos: state.todos.map(item => (item.id === action.payload) ? { ...item, isDone: !item.isDone } : item) } 35 | } 36 | default: { 37 | return state; 38 | } 39 | } 40 | } 41 | 42 | export default reducer; -------------------------------------------------------------------------------- /1_Students App/src/components/student/student.component.tsx: -------------------------------------------------------------------------------- 1 | import './student.css'; 2 | import { IStudent } from '../../@types'; 3 | import CoursesList from '../courses-list/courses-list.component'; 4 | import { Link } from 'react-router-dom'; 5 | import Absents from '../absents/absents.component'; 6 | 7 | interface IProps extends IStudent { 8 | mode: 'details' | 'list'; 9 | } 10 | 11 | const Student = (props: IProps) => { 12 | return ( 13 |
14 |
15 | Student: 16 | { 17 | props.mode === 'list' 18 | ? {props.name.toUpperCase()} 19 | : props.name.toUpperCase() 20 | } 21 |
22 |
23 | Age: {props.age} 24 |
25 |
26 | Is Graduated: {props.isGraduated ? 'Yes' : 'No'} 27 |
28 |
29 | Courses List: 30 | 31 |
32 | { 33 | props.mode === 'list' && 34 | } 35 |
36 | ) 37 | } 38 | 39 | export default Student; -------------------------------------------------------------------------------- /2_Todo App/src/components/form/form.component.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import './form.css'; 3 | import { ITodoItem } from '../types'; 4 | import { ThemeContext } from '../../main'; 5 | 6 | interface IProps { 7 | onSubmit: (item: ITodoItem) => void; 8 | } 9 | 10 | const Form = React.memo((props: IProps) => { 11 | const { theme } = useContext(ThemeContext); 12 | 13 | const handleSubmit = (e: React.FormEvent) => { 14 | e.preventDefault(); 15 | const title: string = e.currentTarget["task"].value; 16 | const isUrgent: boolean = e.currentTarget["urgent"].checked; 17 | if (title.length > 3) { 18 | const newTask: ITodoItem = { 19 | id: Date.now(), 20 | title, 21 | isUrgent, 22 | isDone: false 23 | } 24 | 25 | props.onSubmit(newTask); 26 | } 27 | } 28 | 29 | return ( 30 |
31 | 32 |
33 | 34 | 35 |
36 | 37 |
38 | ) 39 | }); 40 | 41 | export default Form; -------------------------------------------------------------------------------- /memory-card-game/src/screens/screens.css: -------------------------------------------------------------------------------- 1 | .screen.game-screen { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | flex-direction: column; 6 | row-gap: 20px; 7 | margin: 0 auto; 8 | } 9 | 10 | .screen.levels-screen .player { 11 | margin-bottom: 30px; 12 | } 13 | 14 | .screen.levels-screen .player input { 15 | padding: 10px; 16 | font-size: 20px; 17 | border-radius: 10px; 18 | outline: none; 19 | border: 1px solid #aaa; 20 | } 21 | 22 | .screen.levels-screen .levels { 23 | display: flex; 24 | width: 60vmin; 25 | justify-content: space-between; 26 | align-items: center; 27 | column-gap: 30px; 28 | margin: 0 auto; 29 | } 30 | 31 | .screen.levels-screen .levels .level { 32 | padding: 50px; 33 | box-sizing: border-box; 34 | } 35 | 36 | .screen.levels-screen .levels .level:hover { 37 | border: 3px solid gray; 38 | } 39 | 40 | .screen.score-screen .boards>:first-child { 41 | border-right: 2px solid gray; 42 | } 43 | 44 | .screen.score-screen .boards { 45 | display: flex; 46 | flex-direction: row; 47 | justify-content: space-between; 48 | align-items: flex-start; 49 | margin-top: 50px; 50 | } 51 | 52 | .screen.score-screen .boards>div { 53 | padding: 0 30px; 54 | width: 50%; 55 | } 56 | 57 | .screen.score-screen .boards>div h2 { 58 | margin-top: 0; 59 | } -------------------------------------------------------------------------------- /1_Students App/src/hooks/local-storage.hook.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | 3 | const useLocalStorage = (state: any, storageKey: string) => { 4 | const [storedData, setStoredData] = useState({ data: null, loading: true }); 5 | const isInitialMount = useRef(true); 6 | 7 | useEffect(() => { 8 | // Read the data on the first render 9 | const strData = localStorage.getItem(storageKey); 10 | try { 11 | if (strData !== null) { 12 | setStoredData({ data: JSON.parse(strData), loading: false }); 13 | } else { 14 | setStoredData({ data: null, loading: false }); 15 | } 16 | } catch { 17 | setStoredData({ data: strData || null, loading: false }); 18 | } 19 | }, []); 20 | 21 | useEffect(() => { 22 | if (isInitialMount.current) { 23 | isInitialMount.current = false; 24 | return; 25 | } 26 | 27 | if (typeof (state) === 'object') { 28 | localStorage.setItem(storageKey, JSON.stringify(state)); 29 | } else if (state === null) { 30 | localStorage.removeItem(storageKey); 31 | } else { 32 | localStorage.setItem(storageKey, state.toString()); 33 | } 34 | }, [state, storageKey]); 35 | 36 | return { storedData: storedData.data, loading: storedData.loading }; 37 | } 38 | 39 | export default useLocalStorage; -------------------------------------------------------------------------------- /4_news_app/components/header/header.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | box-sizing: border-box; 3 | width: 100%; 4 | height: 56px; 5 | background: #FFFFFFFF; 6 | /* white */ 7 | border-radius: 0px; 8 | box-shadow: 0px 0px 1px #171a1f4a, 0px 0px 2px #171a1f1F; 9 | display: flex; 10 | align-items: center; 11 | justify-content: space-between; 12 | padding: 0 20px; 13 | } 14 | 15 | .header .logo { 16 | display: flex; 17 | align-items: center; 18 | justify-content: flex-start; 19 | column-gap: 10px; 20 | } 21 | 22 | .header .logo img { 23 | height: 36px; 24 | border-radius: 0px; 25 | } 26 | 27 | .header .logo h1 { 28 | font-family: var(--font-roboto); 29 | font-size: 28px; 30 | line-height: 42px; 31 | font-weight: 700; 32 | color: #000000; 33 | } 34 | 35 | .header nav { 36 | display: flex; 37 | align-items: center; 38 | font-family: var(--font-mulish); 39 | font-size: 14px; 40 | line-height: 22px; 41 | font-weight: 400; 42 | opacity: 1; 43 | } 44 | 45 | .header nav a { 46 | display: flex; 47 | align-items: center; 48 | justify-content: center; 49 | padding: 15px 12px; 50 | color: #565E6CFF; 51 | border-radius: 2px; 52 | cursor: pointer; 53 | white-space: nowrap; 54 | text-decoration: none; 55 | } 56 | 57 | .header nav a.selected { 58 | border-bottom: 4px solid #71B2ABFF; 59 | } -------------------------------------------------------------------------------- /4_news_app/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /3_next-example/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /4_news_app/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import { Roboto, Mulish } from 'next/font/google'; 4 | import classNames from "classnames"; 5 | import { ToastContainer } from "react-toastify"; 6 | 7 | const robotoFont = Roboto({ 8 | weight: ['400', '700'], 9 | subsets: ['latin'], 10 | style: ['italic', 'normal'], 11 | fallback: ['Arial', 'Helvetica', 'sans-serif'], 12 | display: 'swap', 13 | variable: '--font-roboto' 14 | }); 15 | 16 | const mulishFont = Mulish({ 17 | weight: ['400', '700'], 18 | subsets: ['latin'], 19 | style: ['italic', 'normal'], 20 | fallback: ['Arial', 'Helvetica', 'sans-serif'], 21 | display: 'swap', 22 | variable: '--font-mulish' 23 | }); 24 | 25 | export const metadata: Metadata = { 26 | title: { 27 | template: '%s | GSG News!', 28 | default: '' 29 | }, 30 | description: 'GSG News, get latest news around the world' 31 | }; 32 | 33 | interface IProps { 34 | children: React.ReactNode; 35 | } 36 | 37 | export default function RootLayout({ children }: IProps) { 38 | return ( 39 | // We used the .variable as class name (not .className) to pass the css variable :) 40 | 41 | 42 | 43 | {children} 44 | 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /1_Students App/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css' 2 | import Main from './screens/Main.screen'; 3 | import About from './screens/About.screen'; 4 | import NotFound from './screens/NotFound.screen'; 5 | import { Route, Routes } from 'react-router-dom'; 6 | import StudentDetails from './screens/StudentDetails.screen'; 7 | import { Role } from './@types'; 8 | import AddStudent from './screens/AddStudent.screen'; 9 | import Login from './screens/Login.screen'; 10 | import NavBar from './components/nav-bar/nav-bar.component'; 11 | import Guarded from './components/common/guarded-route/guarded-route.component'; 12 | 13 | function App() { 14 | const h1Style = { color: '#69247C', fontSize: '24px' }; 15 | 16 | return ( 17 |
18 |

Welcome to GSG React/Next Course

19 | 20 | 21 |
} /> 22 | } /> 23 | } /> 24 | } /> 25 | } /> 26 | } /> 27 | 28 |
29 | ) 30 | } 31 | 32 | export default App; -------------------------------------------------------------------------------- /1_Students App/src/components/nav-bar/nav-bar.component.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import './nav-bar.css'; 3 | import { Link, useLocation } from 'react-router-dom'; 4 | import { AuthContext } from '../../providers/authProvider'; 5 | import { Role } from '../../@types'; 6 | 7 | const NavBar = () => { 8 | const location = useLocation(); 9 | const { user, logout } = useContext(AuthContext); 10 | 11 | const handleLogout = (e: React.MouseEvent) => { 12 | e.preventDefault(); 13 | logout(); 14 | } 15 | 16 | return ( 17 | 38 | ) 39 | } 40 | 41 | export default NavBar -------------------------------------------------------------------------------- /1_Students App/src/state/reducer.ts: -------------------------------------------------------------------------------- 1 | import { IStudent } from "../@types"; 2 | 3 | export type State = { 4 | studentsList: IStudent[]; 5 | totalAbsents: number; 6 | }; 7 | 8 | export type Action = 9 | | { type: "INIT"; payload: IStudent[] } 10 | | { type: "ADD_STUDENT"; payload: IStudent } 11 | | { type: "REMOVE_FIRST" } 12 | | { type: "UPDATE_ABSENTS"; payload: { id: string; change: number } }; 13 | 14 | export const stateReducer = (state: State, action: Action): State => { 15 | switch (action.type) { 16 | case "INIT": { 17 | const totalAbsents: number = action.payload.reduce( 18 | (prev, cur) => prev + cur.absents, 0); 19 | return { studentsList: action.payload, totalAbsents }; 20 | } 21 | case "ADD_STUDENT": 22 | return { 23 | ...state, 24 | studentsList: [action.payload, ...state.studentsList], 25 | }; 26 | case "REMOVE_FIRST": 27 | return { 28 | ...state, 29 | studentsList: state.studentsList.slice(1), 30 | }; 31 | case "UPDATE_ABSENTS": 32 | return { 33 | studentsList: state.studentsList.map((student) => 34 | student.id === action.payload.id 35 | ? { ...student, absents: student.absents + action.payload.change } 36 | : student 37 | ), 38 | totalAbsents: state.totalAbsents + action.payload.change, 39 | }; 40 | default: 41 | return state; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /1_Students App/src/components/courses-list-form/courses-list-form.component.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | 3 | interface IProps { 4 | value: string[]; 5 | onSubmit: (list: string[]) => void; 6 | } 7 | 8 | const CoursesListForm = (props: IProps) => { 9 | const [courseList, setCoursesList] = useState(props.value); 10 | const inputRef = useRef(null); 11 | 12 | useEffect(() => { 13 | setCoursesList(props.value); 14 | }, [props.value]); 15 | 16 | const handleSubmit = (event: React.FormEvent) => { 17 | event.preventDefault(); 18 | const newCourse = event.currentTarget["courseName"].value; 19 | const newList = [...courseList, newCourse]; 20 | setCoursesList(newList); 21 | props.onSubmit(newList); 22 | 23 | if (inputRef.current) { 24 | inputRef.current.value = ""; 25 | } 26 | } 27 | 28 | return ( 29 |
30 |
31 |
32 | 33 | 34 |
35 | 36 |
37 |
    38 | {courseList.map((course, index) =>
  • {course}
  • )} 39 |
40 |
41 | ) 42 | }; 43 | 44 | export default CoursesListForm; -------------------------------------------------------------------------------- /2_Todo App/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testing-ui/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testing-ui/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /1_Students App/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /memory-card-game/src/components/card/card.css: -------------------------------------------------------------------------------- 1 | /* .card { 2 | aspect-ratio: 1; 3 | border: 1px solid gray; 4 | border-radius: 5px; 5 | background-size: contain; 6 | background-position: center; 7 | background-repeat: no-repeat; 8 | cursor: pointer; 9 | } */ 10 | 11 | .card { 12 | aspect-ratio: 1; 13 | border-radius: 5px; 14 | background-size: contain; 15 | background-position: center; 16 | background-repeat: no-repeat; 17 | cursor: pointer; 18 | position: relative; 19 | } 20 | 21 | .card:hover { 22 | border-width: 3px; 23 | } 24 | 25 | .card-front { 26 | background-color: white; 27 | background-image: url(https://api.clipart.com/img/previews/icon-set-98.png); 28 | } 29 | 30 | .card-back { 31 | transform: rotateY(180deg); 32 | background-color: white; 33 | } 34 | 35 | .card-inner { 36 | width: 100%; 37 | height: 100%; 38 | position: relative; 39 | transform-style: preserve-3d; 40 | transition: transform 0.4s ease-in-out; 41 | border-radius: 15px; 42 | box-shadow: 1px 1px 4px 0px #4a4a4a; 43 | } 44 | 45 | .card.flipped .card-inner { 46 | transform: rotateY(180deg); 47 | } 48 | 49 | .card-front, 50 | .card-back { 51 | position: absolute; 52 | width: 100%; 53 | height: 100%; 54 | backface-visibility: hidden; 55 | border-radius: 6px; 56 | background-size: 100%; 57 | background-repeat: no-repeat; 58 | background-position: center; 59 | border-radius: 15px; 60 | } 61 | 62 | .card-front { 63 | background-size: 60%; 64 | } -------------------------------------------------------------------------------- /memory-card-game/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /4_news_app/app/api/auth/login/route.ts: -------------------------------------------------------------------------------- 1 | import { findUserByEmail } from "@/services/auth"; 2 | import { comparePassword, generateToken } from "@/utils/auth"; 3 | import { cookies } from "next/headers"; 4 | import { NextRequest, NextResponse } from "next/server"; 5 | 6 | const POST = async (request: NextRequest) => { 7 | const { email, password } = await request.json() as { email: string, password: string }; 8 | 9 | if (!email || !password) { 10 | return new NextResponse('Email and Password are required', { status: 400 }) 11 | } 12 | 13 | const user = findUserByEmail(email); 14 | if (!user) { 15 | return new NextResponse('Invalid Credentials!', { status: 401 }); 16 | } 17 | 18 | const isValidPassword = comparePassword(password, user.password || ''); 19 | 20 | if (!isValidPassword) { 21 | return new NextResponse('Invalid Credentials!', { status: 401 }); 22 | } 23 | 24 | delete user.password; 25 | const token = await generateToken(user); 26 | 27 | (await cookies()).set('auth-token', token, { 28 | httpOnly: true, 29 | secure: process.env.NODE_ENV === 'production', 30 | maxAge: 3600 // 1 hour 31 | }); 32 | 33 | return new NextResponse(token, { status: 200 }); 34 | } 35 | 36 | export { POST }; 37 | 38 | // If you want to make logout api 39 | /* 40 | 41 | (await cookies()).set('auth-token', '', { 42 | httpOnly: true, 43 | secure: process.env.NODE_ENV === 'production', 44 | maxAge: 0 // Expire Immediately 45 | }); 46 | 47 | 48 | */ -------------------------------------------------------------------------------- /1_Students App/src/components/add-form/add-form.css: -------------------------------------------------------------------------------- 1 | .wrapper.addForm.closed { 2 | max-height: 32px; 3 | overflow: hidden; 4 | } 5 | 6 | .wrapper.addForm.open { 7 | overflow: hidden; 8 | max-height: 1000px; 9 | } 10 | 11 | .wrapper.addForm { 12 | display: flex; 13 | flex-direction: column; 14 | row-gap: 10px; 15 | margin-bottom: 30px; 16 | border-right: 3px solid gray; 17 | border-radius: 4px; 18 | padding-right: 5px; 19 | background-color: #fff6eb; 20 | transition: 200ms max-height ease-in; 21 | } 22 | 23 | .wrapper.addForm .input { 24 | display: flex; 25 | column-gap: 10px; 26 | } 27 | 28 | .wrapper.addForm label { 29 | min-width: 110px; 30 | display: inline-block; 31 | text-align: left; 32 | } 33 | 34 | .wrapper.addForm .Actions { 35 | display: flex; 36 | justify-content: flex-end; 37 | column-gap: 10px; 38 | } 39 | 40 | .wrapper.addForm .addCourseForm form { 41 | display: flex; 42 | justify-content: flex-start; 43 | column-gap: 20px; 44 | } 45 | 46 | .wrapper.addForm .addCourseForm ul { 47 | display: flex; 48 | list-style-type: none; 49 | flex-wrap: wrap; 50 | gap: 10px; 51 | padding: 0; 52 | } 53 | 54 | .wrapper.addForm .report { 55 | color: #ee5454; 56 | text-align: left; 57 | font-size: 12px; 58 | } 59 | 60 | .wrapper.addForm .report h4 { 61 | margin: 0; 62 | } 63 | 64 | .wrapper.addForm .report p { 65 | margin: 0; 66 | line-height: 1em; 67 | } 68 | 69 | .wrapper.addForm .toggle-btn { 70 | width: 150px; 71 | margin-left: auto; 72 | } -------------------------------------------------------------------------------- /4_news_app/app/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background: #ffffff; 3 | --foreground: #171717; 4 | } 5 | 6 | @media (prefers-color-scheme: dark) { 7 | :root { 8 | --background: #0a0a0a; 9 | --foreground: #ededed; 10 | } 11 | } 12 | 13 | html, 14 | body { 15 | max-width: 100vw; 16 | overflow-x: hidden; 17 | } 18 | 19 | body { 20 | color: var(--foreground); 21 | background: var(--background); 22 | font-family: var(--font-roboto); 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | @media (prefers-color-scheme: dark) { 28 | html { 29 | color-scheme: dark; 30 | } 31 | } 32 | 33 | /* Elements */ 34 | .button { 35 | display: inline-block; 36 | padding: 10px 60px; 37 | display: flex; 38 | align-items: center; 39 | justify-content: center; 40 | font-family: var(--font-mulish); 41 | font-size: 16px; 42 | line-height: 28px; 43 | font-weight: 400; 44 | color: #FFF; 45 | background: #71B2ABFF; 46 | opacity: 1; 47 | border: none; 48 | border-radius: 2px; 49 | cursor: pointer; 50 | box-sizing: border-box; 51 | } 52 | 53 | .button.outline { 54 | color: #71B2ABFF; 55 | background: #FFF; 56 | opacity: 1; 57 | border-radius: 2px; 58 | border: 2px #71B2ABFF solid; 59 | box-sizing: border-box; 60 | } 61 | 62 | .button:hover { 63 | color: #FFF; 64 | background: #50958EFF; 65 | } 66 | 67 | .button:active { 68 | color: #FFF; 69 | background: #45807BFF; 70 | } 71 | 72 | .button:disabled { 73 | opacity: 0.4; 74 | } -------------------------------------------------------------------------------- /testing-ui/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import './App.css' 4 | 5 | function App() { 6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 7 | const [newsList, setNewsList] = useState([]); 8 | 9 | const fetchNews = () => { 10 | fetch('http://127.0.0.1:3000/api/news?category=palestine') 11 | .then(res => res.json()) 12 | .then(res => setNewsList(res.results)); 13 | } 14 | 15 | const submitNews = () => { 16 | fetch('http://127.0.0.1:3000/api/news?category=palestine', { 17 | method: 'POST', 18 | body: JSON.stringify({ 19 | "message": "hi from FE" 20 | }) 21 | }) 22 | .then(res => res.json()) 23 | .then(res => setNewsList(res.results)); 24 | } 25 | 26 | const loadArticle = (slug: string) => { 27 | fetch(`http://127.0.0.1:3000/api/news/${slug}123`) 28 | .then(res => res.json()) 29 | .then(res => console.log(res)); 30 | } 31 | 32 | return ( 33 |
34 |

UI for testing the APIs

35 |
36 | 39 | 42 |
43 |
    44 | { 45 | newsList.map(item =>
  • loadArticle(item.slug)}>{item.title}
  • ) 46 | } 47 |
48 |
49 |
50 | ) 51 | } 52 | 53 | export default App 54 | -------------------------------------------------------------------------------- /4_news_app/app/(main)/news/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { getNewsArticle } from '@/services/news.service'; 2 | import Image from 'next/image'; 3 | import React from 'react'; 4 | import classes from './article.module.css'; 5 | import { Metadata } from 'next'; 6 | 7 | interface IProps { 8 | params: Promise<{ slug: string }> 9 | } 10 | 11 | export const generateMetadata = async (props: IProps): Promise => { 12 | const { slug } = await props.params; 13 | const article = getNewsArticle(slug); 14 | 15 | return { 16 | title: `${article.title}`, 17 | authors: { name: article.author } 18 | } 19 | } 20 | 21 | const NewArticle = async (props: IProps) => { 22 | const slug = (await props.params).slug; 23 | const article = getNewsArticle(slug); 24 | 25 | return ( 26 |
27 |

{article.title}

28 |
29 | Author: {article.author} | {new Date(article.date).toLocaleDateString()} 30 |
31 | article image 39 |
40 |

{article.content}

41 |
42 |
43 | 44 | ) 45 | } 46 | 47 | export default NewArticle -------------------------------------------------------------------------------- /1_Students App/src/screens/StudentDetails.screen.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate, useParams } from 'react-router-dom'; 2 | import Student from '../components/student/student.component' 3 | import { IStudent } from '../@types'; 4 | import { useEffect, useState } from 'react'; 5 | 6 | const StudentDetails = () => { 7 | const { id } = useParams(); 8 | const navigate = useNavigate(); 9 | const [currentStudent, setCurrentStudent] = useState(); 10 | 11 | useEffect(() => { 12 | // find the student with ID id from the local storage database 13 | const studentsListStr = localStorage.getItem("students-list"); 14 | if (studentsListStr) { 15 | const stdList: IStudent[] = JSON.parse(studentsListStr); 16 | const std = stdList.find(item => item.id === id); 17 | if (std) { 18 | setCurrentStudent(std); 19 | } else { 20 | navigate('/404'); 21 | } 22 | } 23 | }, [id]); 24 | 25 | return ( 26 |
27 |

Student Details: {currentStudent?.name}

28 | { 29 | currentStudent && ( 30 | 39 | ) 40 | } 41 |
42 | ) 43 | } 44 | 45 | export default StudentDetails -------------------------------------------------------------------------------- /1_Students App/src/screens/Login.screen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { AuthContext } from '../providers/authProvider'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import { Role } from '../@types'; 5 | 6 | const Login = () => { 7 | const { login } = useContext(AuthContext); 8 | const navigate = useNavigate(); 9 | 10 | const handleLogin = (e: React.FormEvent) => { 11 | e.preventDefault(); 12 | const userName = e.currentTarget['userName'].value; 13 | const role = (e.currentTarget['role'] as any).value; 14 | 15 | if (userName) { 16 | login({ userName, role }); 17 | navigate('/'); 18 | } 19 | } 20 | 21 | return ( 22 |
23 |

Please enter your login data

24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 | 36 |
37 |
38 | 39 |
40 |
41 |
42 | ) 43 | } 44 | 45 | export default Login; -------------------------------------------------------------------------------- /2_Todo App/src/components/todo-item/todo-item.css: -------------------------------------------------------------------------------- 1 | .item-wrapper { 2 | border: 1px solid black; 3 | padding: 10px; 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | } 8 | 9 | .item-wrapper.dark { 10 | border-color: white; 11 | color: white; 12 | } 13 | 14 | .item-wrapper.done { 15 | text-decoration: line-through; 16 | order: 999999; 17 | } 18 | 19 | .item-wrapper.urgent { 20 | border-bottom: 3px solid firebrick; 21 | } 22 | 23 | .item-wrapper>.delete { 24 | cursor: pointer; 25 | } 26 | 27 | .item-wrapper>span { 28 | display: flex; 29 | align-items: center; 30 | } 31 | 32 | .item-wrapper>.item-details { 33 | position: relative; 34 | padding-left: 10px; 35 | } 36 | 37 | .round-checkbox label { 38 | background-color: #fff; 39 | border: 1px solid #ccc; 40 | border-radius: 50%; 41 | cursor: pointer; 42 | height: 20px; 43 | width: 20px; 44 | left: 0; 45 | position: absolute; 46 | top: 0; 47 | } 48 | 49 | .round-checkbox label:after { 50 | border: 2px solid #fff; 51 | border-top: none; 52 | border-right: none; 53 | content: ""; 54 | height: 5px; 55 | width: 10px; 56 | left: 4px; 57 | top: 5px; 58 | opacity: 0; 59 | position: absolute; 60 | transform: rotate(-45deg); 61 | } 62 | 63 | .round-checkbox input[type="checkbox"] { 64 | visibility: hidden; 65 | } 66 | 67 | .round-checkbox input[type="checkbox"]:checked+label { 68 | background-color: #66bb6a; 69 | border-color: #66bb6a; 70 | } 71 | 72 | .round-checkbox input[type="checkbox"]:checked+label:after { 73 | opacity: 1; 74 | } -------------------------------------------------------------------------------- /4_news_app/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /3_next-example/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /1_Students App/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | justify-content: center; 4 | align-items: flex-start; 5 | text-align: center; 6 | background-color: #fff6eb; 7 | } 8 | 9 | #root { 10 | width: 100%; 11 | } 12 | 13 | .main { 14 | padding: 30px 20px; 15 | } 16 | 17 | .main h1 { 18 | margin-top: 0; 19 | } 20 | 21 | .main-screen>.stats, .main-screen>.filter { 22 | display: flex; 23 | justify-content: flex-start; 24 | margin-bottom: 10px; 25 | column-gap: 10px; 26 | align-items: center; 27 | } 28 | 29 | .main-screen>.stats>:last-child { 30 | margin-left: auto; 31 | } 32 | 33 | .main.wrapper { 34 | border-left: 8px solid #DA498D; 35 | } 36 | 37 | .main-screen .list { 38 | flex-wrap: wrap; 39 | display: flex; 40 | justify-content: center; 41 | align-items: stretch; 42 | gap: 20px; 43 | margin-top: 20px; 44 | } 45 | 46 | .main-screen .list>.std-wrapper { 47 | flex: 0; 48 | flex-basis: 43%; 49 | } 50 | 51 | [class$="-screen"] h2 { 52 | font-size: 1.5rem; 53 | font-weight: bold; 54 | color: #DA498D; 55 | text-transform: uppercase; 56 | text-align: left; 57 | text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); 58 | } 59 | 60 | .add-screen, .about-screen { 61 | min-height: 500px; 62 | } 63 | 64 | div.spinner { 65 | border: 8px solid #f3f3f3; 66 | border-top: 8px solid #DA498D; 67 | border-radius: 50%; 68 | width: 50px; 69 | height: 50px; 70 | animation: spin 1s linear infinite; 71 | margin: 30px auto; 72 | } 73 | 74 | @keyframes spin { 75 | 0% { 76 | transform: rotate(0deg); 77 | } 78 | 79 | 100% { 80 | transform: rotate(360deg); 81 | } 82 | } -------------------------------------------------------------------------------- /1_Students App/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | 22 | a:hover { 23 | color: #535bf2; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | display: flex; 29 | place-items: center; 30 | min-width: 320px; 31 | min-height: 100vh; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.3em .7em; 43 | font-size: 14px; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #f9f9f9; 47 | cursor: pointer; 48 | transition: border-color 0.25s; 49 | border-color: #FAC67A; 50 | } 51 | 52 | button:disabled { 53 | pointer-events: none; 54 | cursor: not-allowed; 55 | } 56 | 57 | button:hover { 58 | border-color: #866634; 59 | background-color: #dc9fbc; 60 | } 61 | 62 | input, select { 63 | font-size: 14px; 64 | padding: 5px; 65 | border-radius: 5px; 66 | border: 1px solid #FAC67A; 67 | outline: 0; 68 | } 69 | 70 | @media (prefers-color-scheme: light) { 71 | :root { 72 | color: #213547; 73 | background-color: #ffffff; 74 | } 75 | 76 | a:hover { 77 | color: #747bff; 78 | } 79 | 80 | button { 81 | background-color: #f9f9f9; 82 | } 83 | } -------------------------------------------------------------------------------- /2_Todo App/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default tseslint.config({ 18 | languageOptions: { 19 | // other options... 20 | parserOptions: { 21 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 22 | tsconfigRootDir: import.meta.dirname, 23 | }, 24 | }, 25 | }) 26 | ``` 27 | 28 | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` 29 | - Optionally add `...tseslint.configs.stylisticTypeChecked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: 31 | 32 | ```js 33 | // eslint.config.js 34 | import react from 'eslint-plugin-react' 35 | 36 | export default tseslint.config({ 37 | // Set the react version 38 | settings: { react: { version: '18.3' } }, 39 | plugins: { 40 | // Add the react plugin 41 | react, 42 | }, 43 | rules: { 44 | // other rules... 45 | // Enable its recommended rules 46 | ...react.configs.recommended.rules, 47 | ...react.configs['jsx-runtime'].rules, 48 | }, 49 | }) 50 | ``` 51 | -------------------------------------------------------------------------------- /1_Students App/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default tseslint.config({ 18 | languageOptions: { 19 | // other options... 20 | parserOptions: { 21 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 22 | tsconfigRootDir: import.meta.dirname, 23 | }, 24 | }, 25 | }) 26 | ``` 27 | 28 | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` 29 | - Optionally add `...tseslint.configs.stylisticTypeChecked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: 31 | 32 | ```js 33 | // eslint.config.js 34 | import react from 'eslint-plugin-react' 35 | 36 | export default tseslint.config({ 37 | // Set the react version 38 | settings: { react: { version: '18.3' } }, 39 | plugins: { 40 | // Add the react plugin 41 | react, 42 | }, 43 | rules: { 44 | // other rules... 45 | // Enable its recommended rules 46 | ...react.configs.recommended.rules, 47 | ...react.configs['jsx-runtime'].rules, 48 | }, 49 | }) 50 | ``` 51 | -------------------------------------------------------------------------------- /memory-card-game/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default tseslint.config({ 18 | languageOptions: { 19 | // other options... 20 | parserOptions: { 21 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 22 | tsconfigRootDir: import.meta.dirname, 23 | }, 24 | }, 25 | }) 26 | ``` 27 | 28 | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` 29 | - Optionally add `...tseslint.configs.stylisticTypeChecked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: 31 | 32 | ```js 33 | // eslint.config.js 34 | import react from 'eslint-plugin-react' 35 | 36 | export default tseslint.config({ 37 | // Set the react version 38 | settings: { react: { version: '18.3' } }, 39 | plugins: { 40 | // Add the react plugin 41 | react, 42 | }, 43 | rules: { 44 | // other rules... 45 | // Enable its recommended rules 46 | ...react.configs.recommended.rules, 47 | ...react.configs['jsx-runtime'].rules, 48 | }, 49 | }) 50 | ``` 51 | -------------------------------------------------------------------------------- /memory-card-game/src/screens/levels.screen.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from "react"; 2 | import { GameModeContext } from "../providers/modeProvider"; 3 | import { Link, useNavigate } from "react-router-dom"; 4 | import { ELevels } from "../types/@types"; 5 | 6 | const LevelsScreen = () => { 7 | const { gameMode, setGameMode } = useContext(GameModeContext); 8 | const [error, setError] = useState(''); 9 | const navigate = useNavigate(); 10 | 11 | const handleSelectLevel = (level: ELevels) => { 12 | if (gameMode.playerName.length < 3) { 13 | setError("Please enter your name (at least 3 chars)"); 14 | return; 15 | } 16 | setGameMode(old => ({ ...old, level })); 17 | navigate("/game"); 18 | }; 19 | 20 | const handleChangeName = (event: React.ChangeEvent) => { 21 | const name = event.currentTarget.value || ''; 22 | setGameMode(old => ({ ...old, playerName: name })); 23 | } 24 | 25 | return ( 26 |
27 |

Flip Card Game

28 | 29 |

30 |
31 |

Please enter your name

32 | 33 | {Boolean(error) &&

{error}

} 34 |
35 |

Please select your level

36 |
37 | 38 | 39 | 40 |
41 |
42 | ) 43 | } 44 | 45 | export default LevelsScreen; -------------------------------------------------------------------------------- /4_news_app/app/(main)/news/[slug]/article.module.css: -------------------------------------------------------------------------------- 1 | .articleContainer { 2 | max-width: 800px; 3 | margin: 2rem auto; 4 | padding: 2rem; 5 | background-color: white; 6 | border-radius: 8px; 7 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); 8 | } 9 | 10 | .articleTitle { 11 | color: #71b2ab; 12 | font-size: 2.5rem; 13 | font-weight: 700; 14 | margin-bottom: 1rem; 15 | line-height: 1.2; 16 | border-bottom: 2px solid #a3d0ca; 17 | padding-bottom: 0.5rem; 18 | } 19 | 20 | .articleAddress { 21 | font-style: normal; 22 | color: #666; 23 | margin-bottom: 1.5rem; 24 | font-size: 0.9rem; 25 | } 26 | 27 | .articleAddress cite { 28 | font-style: normal; 29 | color: #71b2ab; 30 | font-weight: 500; 31 | } 32 | 33 | .articleImage { 34 | width: 100%; 35 | height: auto; 36 | border-radius: 8px; 37 | margin-bottom: 1.5rem; 38 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 39 | } 40 | 41 | .articleMain { 42 | font-size: 1.1rem; 43 | line-height: 1.6; 44 | color: #333; 45 | } 46 | 47 | .articleParagraph { 48 | margin-bottom: 1.5rem; 49 | } 50 | 51 | .articleParagraph:last-child { 52 | margin-bottom: 0; 53 | } 54 | 55 | /* Responsive adjustments */ 56 | @media (max-width: 768px) { 57 | .articleContainer { 58 | padding: 1.5rem; 59 | margin: 1rem; 60 | } 61 | 62 | .articleTitle { 63 | font-size: 2rem; 64 | } 65 | 66 | .articleMain { 67 | font-size: 1rem; 68 | } 69 | } 70 | 71 | /* Optional: Add some subtle animations */ 72 | .articleContainer { 73 | animation: fadeIn 0.5s ease-in-out; 74 | } 75 | 76 | @keyframes fadeIn { 77 | from { 78 | opacity: 0; 79 | transform: translateY(20px); 80 | } 81 | to { 82 | opacity: 1; 83 | transform: translateY(0); 84 | } 85 | } 86 | 87 | .articleImage { 88 | transition: transform 0.3s ease; 89 | } 90 | 91 | .articleImage:hover { 92 | transform: scale(1.02); 93 | } 94 | 95 | -------------------------------------------------------------------------------- /memory-card-game/src/App.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Rocher'; 3 | src: url(https://assets.codepen.io/9632/RocherColorGX.woff2); 4 | } 5 | 6 | * { 7 | font-family: 'Courier New', Courier, monospace; 8 | } 9 | 10 | body { 11 | background: linear-gradient(174deg, #d9d9d9, #bfbeff); 12 | height: 100vh; 13 | overflow: auto; 14 | margin: 0; 15 | padding: 0; 16 | color: #333; 17 | } 18 | 19 | #root { 20 | max-width: 1280px; 21 | margin: 0 auto; 22 | padding: 2rem; 23 | text-align: center; 24 | } 25 | 26 | .placeholder { 27 | border: 1px solid #cdcdcd; 28 | } 29 | 30 | /* Copied form the web :-)*/ 31 | button { 32 | align-items: center; 33 | appearance: none; 34 | background-color: #FCFCFD; 35 | border-radius: 4px; 36 | border-width: 0; 37 | box-shadow: rgba(45, 35, 66, 0.4) 0 2px 4px, rgba(45, 35, 66, 0.3) 0 7px 13px -3px, #D6D6E7 0 -3px 0 inset; 38 | box-sizing: border-box; 39 | color: #36395A; 40 | cursor: pointer; 41 | display: inline-flex; 42 | font-family: "JetBrains Mono", monospace; 43 | height: 48px; 44 | justify-content: center; 45 | line-height: 1; 46 | list-style: none; 47 | overflow: hidden; 48 | padding-left: 16px; 49 | padding-right: 16px; 50 | position: relative; 51 | text-align: left; 52 | text-decoration: none; 53 | transition: box-shadow .15s, transform .15s; 54 | user-select: none; 55 | -webkit-user-select: none; 56 | touch-action: manipulation; 57 | white-space: nowrap; 58 | will-change: box-shadow, transform; 59 | font-size: 18px; 60 | } 61 | 62 | button:focus { 63 | box-shadow: #D6D6E7 0 0 0 1.5px inset, rgba(45, 35, 66, 0.4) 0 2px 4px, rgba(45, 35, 66, 0.3) 0 7px 13px -3px, #D6D6E7 0 -3px 0 inset; 64 | } 65 | 66 | button:hover { 67 | box-shadow: rgba(45, 35, 66, 0.4) 0 4px 8px, rgba(45, 35, 66, 0.3) 0 7px 13px -3px, #D6D6E7 0 -3px 0 inset; 68 | transform: translateY(-2px); 69 | } 70 | 71 | button:active { 72 | box-shadow: #D6D6E7 0 3px 7px inset; 73 | transform: translateY(2px); 74 | } -------------------------------------------------------------------------------- /memory-card-game/src/screens/score-board.screen.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { IScore } from '../types/@types'; 3 | import ScoreList from '../components/score-list/score-list'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | const getScores = () => { 7 | const scores: IScore[] = JSON.parse(localStorage.getItem('flip-cards-scores') || '[]'); 8 | return scores; 9 | } 10 | 11 | function getTopPlayers(scores: IScore[]): IScore[] { 12 | return scores 13 | .sort((a, b) => 14 | b.level - a.level || // Higher level first 15 | a.finishTime - b.finishTime || // Lower finish time first 16 | a.wrongMoves - b.wrongMoves // Lower wrong moves first 17 | ) 18 | .slice(0, 3); 19 | } 20 | 21 | 22 | const ScoreBoardScreen = () => { 23 | const [savedScores, setSavedScores] = useState([]); 24 | const [topPlayers, setTopPlayers] = useState([]); 25 | 26 | useEffect(() => { 27 | const ss = getScores(); 28 | setSavedScores(ss); 29 | const tp = getTopPlayers(ss); 30 | setTopPlayers(tp); 31 | }, []); 32 | 33 | const handleClearAll = () => { 34 | if (confirm('Sure?')) { 35 | localStorage.removeItem('flip-cards-scores'); 36 | } 37 | } 38 | 39 | return ( 40 |
41 |

Score Board

42 | 43 |

44 | 45 | { 46 | !savedScores.length 47 | ?

No Scores Found!

48 | : ( 49 |
50 |
51 |

All Games

52 | 53 |
54 |
55 |

Top 3 Games

56 | 57 |
58 |
59 | ) 60 | } 61 | 62 |
63 | ) 64 | } 65 | 66 | export default ScoreBoardScreen; -------------------------------------------------------------------------------- /3_next-example/app/reach/portfolio/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const page = () => { 4 | return ( 5 |
6 |
7 |
8 |

John Doe

9 |

Frontend Developer

10 |
11 | 12 |
13 |

14 | Passionate about building beautiful and functional user interfaces. Loves Tailwind CSS and modern web technologies. 15 |

16 |
17 |

Skills

18 |
19 | JavaScript 20 | React 21 | Tailwind CSS 22 | Node.js 23 |
24 |
25 |
26 | 29 |
30 |
31 |
32 |

Follow me on Twitter

33 |
34 |
35 |
36 | ) 37 | } 38 | 39 | export default page; -------------------------------------------------------------------------------- /1_Students App/src/components/absents/absents.component.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useRef, useState } from 'react' 2 | import { IStudent } from '../../@types'; 3 | import { AuthContext } from '../../providers/authProvider'; 4 | import { StateContext } from '../../providers/stateProvider'; 5 | 6 | type IProps = IStudent 7 | 8 | const Absents = (props: IProps) => { 9 | const [absents, setAbsents] = useState(props.absents); 10 | const [absentColor, setAbsentColor] = useState('#213547'); 11 | const prevAbsents = useRef(props.absents); 12 | const { user } = useContext(AuthContext); 13 | const { dispatch } = useContext(StateContext); 14 | 15 | 16 | useEffect(() => { 17 | if (absents >= 10) { 18 | setAbsentColor('#ff0000'); 19 | } else if (absents >= 7) { 20 | setAbsentColor('#fd9c0e'); 21 | } else if (absents >= 5) { 22 | setAbsentColor('#d6c728'); 23 | } else { 24 | setAbsentColor('#213547'); 25 | } 26 | }, [absents]); 27 | 28 | const addAbsent = () => { 29 | prevAbsents.current = absents; 30 | setAbsents(absents + 1); 31 | if (dispatch) { 32 | dispatch({ type: "UPDATE_ABSENTS", payload: { id: props.id, change: +1 } }); 33 | } 34 | } 35 | 36 | const removeAbsent = () => { 37 | if (absents - 1 >= 0) { 38 | prevAbsents.current = absents; 39 | setAbsents(absents - 1); 40 | if (dispatch) { 41 | dispatch({ type: "UPDATE_ABSENTS", payload: { id: props.id, change: -1 } }); 42 | } 43 | } 44 | } 45 | 46 | const resetAbsent = () => { 47 | prevAbsents.current = absents; 48 | setAbsents(0); 49 | if (dispatch) { 50 | dispatch({ type: "UPDATE_ABSENTS", payload: { id: props.id, change: -absents } }); 51 | } 52 | } 53 | 54 | return ( 55 |
56 | Absents: {absents} 57 | 58 | 59 | 60 |
61 | ) 62 | } 63 | 64 | export default Absents; -------------------------------------------------------------------------------- /testing-ui/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: 13 | 14 | ```js 15 | export default tseslint.config({ 16 | extends: [ 17 | // Remove ...tseslint.configs.recommended and replace with this 18 | ...tseslint.configs.recommendedTypeChecked, 19 | // Alternatively, use this for stricter rules 20 | ...tseslint.configs.strictTypeChecked, 21 | // Optionally, add this for stylistic rules 22 | ...tseslint.configs.stylisticTypeChecked, 23 | ], 24 | languageOptions: { 25 | // other options... 26 | parserOptions: { 27 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 28 | tsconfigRootDir: import.meta.dirname, 29 | }, 30 | }, 31 | }) 32 | ``` 33 | 34 | You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: 35 | 36 | ```js 37 | // eslint.config.js 38 | import reactX from 'eslint-plugin-react-x' 39 | import reactDom from 'eslint-plugin-react-dom' 40 | 41 | export default tseslint.config({ 42 | plugins: { 43 | // Add the react-x and react-dom plugins 44 | 'react-x': reactX, 45 | 'react-dom': reactDom, 46 | }, 47 | rules: { 48 | // other rules... 49 | // Enable its recommended typescript rules 50 | ...reactX.configs['recommended-typescript'].rules, 51 | ...reactDom.configs.recommended.rules, 52 | }, 53 | }) 54 | ``` 55 | -------------------------------------------------------------------------------- /memory-card-game/src/providers/reducer.ts: -------------------------------------------------------------------------------- 1 | import { ELevels, ICard } from "../types/@types"; 2 | import { createGameBoard } from "../utils/game.util"; 3 | 4 | export interface IGameState { 5 | initialized: boolean; 6 | cards: ICard[]; 7 | openCards: number[]; 8 | } 9 | 10 | export type Action = 11 | { type: 'init', payload: { level: ELevels } } 12 | | { type: 'flip-card', payload: { id: number, index: number } } 13 | | { type: 'hide-mismatch' }; 14 | 15 | export const gameReducer = (state: IGameState, action: Action): IGameState => { 16 | switch (action.type) { 17 | case 'init': { 18 | const cards = createGameBoard(action.payload.level); 19 | return { ...state, initialized: true, cards }; 20 | } 21 | 22 | case 'flip-card': { 23 | if (state.openCards.includes(action.payload.index) || 24 | state.openCards.length === 2 || 25 | state.cards[action.payload.index].revealed 26 | || state.cards[action.payload.index].visible) { 27 | return state; 28 | } 29 | 30 | let openCards = [...state.openCards, action.payload.index]; 31 | const cards: ICard[] = [...state.cards]; 32 | if (openCards.length === 2) { 33 | cards[openCards[0]].visible = true; 34 | cards[openCards[1]].visible = true; 35 | if (cards[openCards[0]].id === cards[openCards[1]].id) { 36 | cards[openCards[0]].revealed = true; 37 | cards[openCards[1]].revealed = true; 38 | openCards = []; 39 | } 40 | } else { 41 | if (!cards[action.payload.index].visible) { 42 | cards[action.payload.index].visible = true; 43 | } 44 | } 45 | return { ...state, openCards, cards }; 46 | } 47 | case 'hide-mismatch': { 48 | if (state.cards[state.openCards[0]].id !== state.cards[state.openCards[1]].id) { 49 | const cards: ICard[] = [...state.cards]; 50 | cards[state.openCards[0]].visible = false; 51 | cards[state.openCards[1]].visible = false; 52 | return { ...state, openCards: [], cards }; 53 | } else { 54 | return state; 55 | } 56 | } 57 | default: 58 | return state 59 | } 60 | } -------------------------------------------------------------------------------- /4_news_app/services/news.service.ts: -------------------------------------------------------------------------------- 1 | import sql from 'better-sqlite3'; 2 | const db = sql('news.db'); 3 | 4 | const getNewsByCategory = (category: string): News.Item_[] => { 5 | const results = db.prepare('SELECT * FROM articles WHERE category = ?').all(category); 6 | return results as News.Item_[]; 7 | } 8 | 9 | const getNewsArticle = (slug: string): News.Item_ => { 10 | return db.prepare('SELECT * FROM articles WHERE slug = ?').get(slug) as News.Item_; 11 | } 12 | 13 | const insertArticle = (newArticle: News.Item_) => { 14 | db.prepare(` 15 | INSERT INTO articles 16 | (slug, title, image, summary, content, author, author_email, date, category) 17 | VALUES ( 18 | @slug, 19 | @title, 20 | @image, 21 | @summary, 22 | @content, 23 | @author, 24 | @author_email, 25 | @date, 26 | @category 27 | )`) 28 | .run(newArticle); 29 | } 30 | 31 | const api_key = 'pub_701076cdd4cdeaa56df41b17fae04f1ce8350'; 32 | 33 | /** 34 | * @deprecated we will use our db to fetch data 35 | */ 36 | const fetchNews = async (category: string, country: string) => { 37 | const res = await fetch( 38 | `https://newsdata.io/api/1/latest?apikey=${api_key}&category=${category}&country=${country}`, 39 | { method: 'GET', cache: 'no-store' } 40 | ); 41 | 42 | const newsRes = (await res.json()) as News.IResponse; 43 | let latestNews: News.Item[] = []; 44 | if (newsRes.status === 'success') { 45 | latestNews = newsRes.results.map(item => ( 46 | { 47 | id: item.article_id, 48 | title: item.title, 49 | img: item.image_url, 50 | content: item.description 51 | } 52 | )); 53 | } else { 54 | // triggering notFound manually 55 | // notFound(); 56 | } 57 | 58 | // throw new Error("Something went wrong while fetching the News from server [xa2658]"); 59 | 60 | // The goal of the promise below is to make the response slower (just to demo loading status) 61 | return new Promise((resolve) => setTimeout(() => { 62 | resolve(latestNews); 63 | }, 1000)); 64 | } 65 | 66 | export { 67 | fetchNews, 68 | getNewsByCategory, 69 | getNewsArticle, 70 | insertArticle 71 | } -------------------------------------------------------------------------------- /4_news_app/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { verifyToken } from "./utils/auth"; 3 | import { cookies } from "next/headers"; 4 | 5 | const authenticateUser = async (token: string | undefined, request: NextRequest) => { 6 | 7 | // verify there is a token 8 | if (!token) { 9 | // else redirect to login page! 10 | return NextResponse.redirect(new URL(`/user/login?msg=${encodeURIComponent('You must be logged in to access this!')}`, request.url)); 11 | } 12 | 13 | // verify the token is valid 14 | const user = await verifyToken(token); 15 | 16 | if (!user) { 17 | // else redirect to login page! 18 | (await cookies()).delete('auth-token'); 19 | return NextResponse.redirect(new URL(`/user/login?msg=${encodeURIComponent('Your session is expired!')}`, request.url)); 20 | } 21 | return user; 22 | } 23 | 24 | export const middleware = async (request: NextRequest) => { 25 | // using the middle for logging 26 | console.log(`A request is made to ${request.nextUrl.pathname}`); 27 | // read cookies from the request 28 | const token = request.cookies.get('auth-token')?.value; 29 | 30 | switch (request.nextUrl.pathname) { 31 | case '/admin': 32 | const res = await authenticateUser(token, request); 33 | if (res instanceof (NextResponse)) { 34 | return res; 35 | } 36 | 37 | // check the role stored the in token 38 | if (res.role !== 'admin') { 39 | // else redirect to home page! 40 | return NextResponse.redirect(new URL('/', request.url)); 41 | } 42 | // if role is admin => move to next page (admin page) 43 | break; 44 | case '/add-news': { 45 | const res = await authenticateUser(token, request) as News.IUser; 46 | if (res instanceof (NextResponse)) { 47 | return res; 48 | } 49 | 50 | if (!['editor', 'admin'].includes(res.role)) { 51 | return NextResponse.redirect(new URL('/', request.url)); 52 | } 53 | break; 54 | } 55 | 56 | default: 57 | break; 58 | } 59 | 60 | 61 | return NextResponse.next(); 62 | } 63 | 64 | export const config = { 65 | matcher: ['/news/:path*', '/admin/:path*', '/:path*'] 66 | } -------------------------------------------------------------------------------- /4_news_app/controllers/news-actions.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import { ALLOWED_CATEGORIES } from "@/constants/data"; 4 | import { insertArticle } from "@/services/news.service"; 5 | import { redirect } from "next/navigation"; 6 | import slugify from "slugify"; 7 | import xss from "xss"; 8 | import fs from 'node:fs'; 9 | 10 | const addArticle = async (prevState: { errors: string[] }, formData: FormData) => { 11 | const title = xss(formData.get('title')?.toString() || ''); 12 | 13 | const newArticle: News.Item_ = { 14 | title, 15 | image: '', 16 | summary: xss(formData.get('summary')?.toString() || ''), 17 | content: xss(formData.get('content')?.toString() || ''), 18 | date: new Date(formData.get('date')?.toString() || '').getTime(), 19 | author: formData.get('author')?.toString() || '', 20 | author_email: formData.get('author_email')?.toString() || '', 21 | category: formData.get('category')?.toString() || 'global', 22 | slug: slugify(title, { lower: true }) 23 | }; 24 | 25 | const imgFile = formData.get('image') as File; 26 | const fileExtension = imgFile.name.split('.').pop(); 27 | const fileName = `${newArticle.slug}.${fileExtension}`; 28 | const stream = fs.createWriteStream(`public/images/${fileName}`); 29 | const bufferedImage = await imgFile.arrayBuffer() 30 | stream.write(Buffer.from(bufferedImage), (error) => { 31 | if (error) { 32 | throw new Error('Error uploading Image!'); 33 | } 34 | }); 35 | 36 | newArticle.image = `/images/${fileName}`; 37 | 38 | const errors: string[] = []; 39 | 40 | if (!newArticle.title.length) { 41 | errors.push("The title should not be empty"); 42 | } 43 | 44 | if (newArticle.title.length > 300) { 45 | errors.push("The title should be less than 300 chars length"); 46 | } 47 | 48 | if (!ALLOWED_CATEGORIES.includes(newArticle.category)) { 49 | errors.push("The category you've provided is not allowed!"); 50 | } 51 | 52 | if (newArticle.date > Date.now()) { 53 | errors.push("The date should not be in the future!"); 54 | } 55 | 56 | if (errors.length) { 57 | return { 58 | errors 59 | } 60 | } 61 | 62 | await new Promise((resolve) => setTimeout(resolve, 2000)); 63 | insertArticle(newArticle); 64 | redirect(`/news/${newArticle.slug}`); 65 | } 66 | 67 | export { 68 | addArticle 69 | }; -------------------------------------------------------------------------------- /memory-card-game/src/hooks/game-logic.hook.ts: -------------------------------------------------------------------------------- 1 | import { useReducer, useContext, useRef, useEffect } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { GameModeContext } from "../providers/modeProvider"; 4 | import { gameReducer } from "../providers/reducer"; 5 | import { checkedFinished } from "../utils/game.util"; 6 | import { IScore } from "../types/@types"; 7 | 8 | const storeScore = (score: IScore) => { 9 | const scores: IScore[] = JSON.parse(localStorage.getItem('flip-cards-scores') || '[]'); 10 | scores.push(score); 11 | localStorage.setItem('flip-cards-scores', JSON.stringify(scores)); 12 | } 13 | 14 | const useGameLogic = () => { 15 | const [state, dispatch] = useReducer(gameReducer, { cards: [], initialized: false, openCards: [] }); 16 | const { gameMode, setGameMode, resetGame } = useContext(GameModeContext); 17 | const timerRef = useRef(0); 18 | const navigate = useNavigate(); 19 | 20 | useEffect(() => { 21 | if (!gameMode.playerName) { 22 | navigate('/'); 23 | return; 24 | } 25 | 26 | if (!state.initialized) { 27 | dispatch({ type: 'init', payload: { level: gameMode.level } }); 28 | resetGame(); 29 | timerRef.current = setInterval(() => { 30 | setGameMode(old => ({ ...old, time: old.time + 1 })); 31 | }, 1000); 32 | } 33 | 34 | return () => { 35 | clearInterval(timerRef.current); 36 | } 37 | }, []); 38 | 39 | useEffect(() => { 40 | if (!state.initialized) return; 41 | 42 | if (state.openCards.length === 2) { 43 | setGameMode(old => ({ ...old, wrongMoves: old.wrongMoves + 1 })); 44 | setTimeout(() => { 45 | dispatch({ type: 'hide-mismatch' }); 46 | }, 1500); 47 | } 48 | 49 | const isFinished = checkedFinished(state.cards); 50 | if (isFinished) { 51 | setGameMode(old => ({ ...old, finished: true })); 52 | clearInterval(timerRef.current); 53 | 54 | const score: IScore = { 55 | finishTime: gameMode.time, 56 | level: gameMode.level, 57 | playerName: gameMode.playerName, 58 | wrongMoves: gameMode.wrongMoves 59 | } 60 | storeScore(score); 61 | 62 | setTimeout(() => { 63 | navigate('/score-board'); 64 | }, 3000); 65 | } 66 | }, [state.openCards]); 67 | 68 | return { 69 | state, 70 | gameMode, 71 | dispatch 72 | } 73 | 74 | } 75 | 76 | export default useGameLogic; -------------------------------------------------------------------------------- /4_news_app/components/add-article/add-article.module.css: -------------------------------------------------------------------------------- 1 | .newsForm { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 1.5rem; 5 | } 6 | 7 | .formGroup { 8 | display: flex; 9 | flex-direction: column; 10 | gap: 0.5rem; 11 | } 12 | 13 | .formLabel { 14 | font-weight: 500; 15 | color: #333333; 16 | font-size: 0.95rem; 17 | } 18 | 19 | .formInput, 20 | .formTextarea, 21 | .formSelect { 22 | padding: 0.75rem; 23 | border: 1px solid #dddddd; 24 | border-radius: 4px; 25 | font-size: 1rem; 26 | transition: all 0.3s ease; 27 | } 28 | 29 | .formInput:focus, 30 | .formTextarea:focus, 31 | .formSelect:focus { 32 | outline: none; 33 | border-color: #71b2ab; 34 | box-shadow: 0 0 0 2px rgba(113, 178, 171, 0.2); 35 | } 36 | 37 | .formTextarea { 38 | resize: vertical; 39 | min-height: 120px; 40 | } 41 | 42 | .formSelect { 43 | background-color: white; 44 | cursor: pointer; 45 | } 46 | 47 | .formSelect option { 48 | padding: 0.5rem; 49 | } 50 | 51 | .formHidden { 52 | display: none; 53 | } 54 | 55 | .submitButton { 56 | background-color: #71b2ab; 57 | color: white; 58 | border: none; 59 | padding: 0.75rem 1.5rem; 60 | font-size: 1rem; 61 | font-weight: 500; 62 | border-radius: 4px; 63 | cursor: pointer; 64 | transition: background-color 0.3s ease; 65 | align-self: flex-start; 66 | margin-top: 0.5rem; 67 | } 68 | 69 | .submitButton:hover { 70 | background-color: #5a8f89; 71 | } 72 | 73 | .submitButton:disabled { 74 | background-color: #9b9c9c; 75 | cursor: not-allowed; 76 | } 77 | 78 | .submitButton:focus { 79 | outline: none; 80 | box-shadow: 0 0 0 3px rgba(113, 178, 171, 0.3); 81 | } 82 | 83 | /* Responsive adjustments */ 84 | @media (max-width: 768px) { 85 | .newsFormContainer { 86 | padding: 1.5rem; 87 | margin: 1rem; 88 | } 89 | 90 | .formTitle { 91 | font-size: 1.75rem; 92 | } 93 | } 94 | 95 | /* Optional: Add some animation for form elements */ 96 | .formInput, 97 | .formTextarea, 98 | .formSelect { 99 | transform: translateY(0); 100 | transition: transform 0.2s ease, border-color 0.3s ease, box-shadow 0.3s ease; 101 | } 102 | 103 | .formInput:focus, 104 | .formTextarea:focus, 105 | .formSelect:focus { 106 | transform: translateY(-2px); 107 | } 108 | 109 | 110 | .errors { 111 | font-size: 14px; 112 | color: firebrick; 113 | list-style-type: disc; 114 | padding-left: 20px; 115 | } -------------------------------------------------------------------------------- /3_next-example/app/antd/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React from 'react' 3 | import { Button, Form, Checkbox, Input } from 'antd'; 4 | import { SyncOutlined } from '@ant-design/icons'; 5 | import { DatePicker } from 'antd'; 6 | import type { FormProps } from 'antd'; 7 | 8 | const { RangePicker } = DatePicker; 9 | 10 | const Page = () => { 11 | type FieldType = { 12 | username?: string; 13 | password?: string; 14 | remember?: string; 15 | }; 16 | 17 | const onFinish: FormProps['onFinish'] = (values) => { 18 | console.log('Success:', values); 19 | }; 20 | 21 | const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => { 22 | console.log('Failed:', errorInfo); 23 | }; 24 | 25 | return ( 26 |
27 |
28 | 29 | 32 | { 34 | 35 | console.log(v?.[0]?.toDate()); 36 | console.log(v?.[1]?.toDate()); 37 | }} 38 | /> 39 |
40 |
41 |
51 | 52 | label="Username" 53 | name="username" 54 | rules={[{ required: true, message: 'Please input your username!' }]} 55 | > 56 | 57 | 58 | 59 | 60 | label="Password" 61 | name="password" 62 | rules={[{ required: true, message: 'Please input your password!' }]} 63 | > 64 | 65 | 66 | 67 | name="remember" valuePropName="checked" label={null}> 68 | Remember me 69 | 70 | 71 | 72 | 75 | 76 | 77 |
78 |
79 | ) 80 | } 81 | 82 | export default Page -------------------------------------------------------------------------------- /2_Todo App/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useContext, useEffect, useReducer, useRef, useState } from 'react' 2 | import './App.css' 3 | import Dashboard from './components/dashboard/dashboard-component' 4 | import Form from './components/form/form.component' 5 | import TodoList from './components/todo-list/todo-list.component' 6 | import { ITodoItem } from './components/types' 7 | import useLocalStorage from './hooks/local-storage.hook' 8 | import reducer from './state/reducer' 9 | import { ThemeContext } from './main' 10 | 11 | function App() { 12 | const [date, setDate] = useState(''); 13 | const timerRef = useRef(); 14 | const [state, dispatch] = useReducer(reducer, { todos: [], userName: 'Ahmad' }); 15 | const { theme, setTheme } = useContext(ThemeContext); 16 | 17 | const { storedData } = useLocalStorage(state.todos, 'todo-list'); 18 | 19 | useEffect(() => { 20 | document.body.style.backgroundColor = theme === 'light' ? '#fff' : '#000'; 21 | }, [theme]); 22 | 23 | useEffect(() => { 24 | dispatch({ type: 'INIT_TODOS', payload: storedData || [] }); 25 | }, [storedData]); 26 | 27 | useEffect(() => { 28 | timerRef.current = setInterval(() => { 29 | setDate(new Date().toLocaleTimeString()); 30 | }, 1000); 31 | }, []); 32 | 33 | const stopTime = () => { 34 | if (timerRef.current) { 35 | clearInterval(timerRef.current); 36 | } 37 | } 38 | 39 | const handleNewItem = useCallback((item: ITodoItem) => { 40 | dispatch({ type: 'ADD_TODO', payload: item }); 41 | }, [state.todos]); 42 | 43 | const handleTaskToggle = (e: React.ChangeEvent) => { 44 | const itemId = Number(e.target.dataset["itemId"]); 45 | dispatch({ type: 'TOGGLE_TODO', payload: itemId }); 46 | } 47 | 48 | const handleDelete = (index: number) => { 49 | // This will delete the item at index! 50 | const itemId = state.todos[index].id; 51 | dispatch({ type: 'REMOVE_TODO', payload: itemId }); 52 | } 53 | 54 | const toggleTheme = () => { 55 | setTheme(old => (old === 'light' ? 'dark' : 'light')); 56 | } 57 | 58 | return ( 59 |
60 |

61 | Todo App - Hello {state.userName} - {date} 62 | 63 | 64 |

65 |
66 | 67 | 68 |
69 | ) 70 | } 71 | 72 | export default App 73 | -------------------------------------------------------------------------------- /4_news_app/components/add-article/AddArticle.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { useActionState } from 'react'; 4 | import classes from './add-article.module.css'; 5 | import { addArticle } from '@/controllers/news-actions'; 6 | import SubmitArticle from './SubmitArticle'; 7 | 8 | const AddArticleForm = () => { 9 | 10 | const [state, formAction] = useActionState(addArticle, { errors: [] }); 11 | 12 | return ( 13 | 14 |
15 | 18 | 19 |
20 |
21 | 24 | 25 |
26 |
27 | 30 | 31 |
32 |
33 | 36 |