├── .env ├── .eslintrc.json ├── .github └── workflows │ ├── build.yml │ └── playwright.yml ├── .gitignore ├── LICENSE ├── README.md ├── components ├── ContentEditor │ ├── ContentEditor.tsx │ └── index.ts ├── Date │ ├── date.module.css │ ├── date.tsx │ └── index.ts ├── Footer │ ├── Footer.module.css │ ├── Footer.tsx │ └── index.ts ├── Form │ ├── CommentForm.jsx │ └── Form.jsx ├── Header │ ├── Header.tsx │ └── index.ts ├── Menu │ ├── Menu.tsx │ ├── index.ts │ └── menu.module.css └── index.ts ├── data ├── infrastructure │ ├── Auth0_Docs.md │ ├── GitHub_Actions.md │ └── Vercel-Doc.md ├── main.json ├── programming │ ├── React_TypeScript_Cheatsheets.md │ ├── Shell_scripting_with_NodeJS.md │ └── Tackling_TypeScript.md ├── soft-skills │ ├── Pomodoro_Technique.md │ ├── jedi-technics.md │ └── junior-dev-resources.md └── test-works │ └── my-first-test-work.md ├── e2e └── example.spec.ts ├── layouts ├── PageLayout │ ├── PagesLayout.tsx │ ├── index.ts │ └── pageLayout.module.css └── index.ts ├── lib ├── dbConnect.js ├── getAllFilesIds.ts └── parseMarkdownFile.ts ├── models └── Comments.js ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── api │ ├── comments │ │ ├── [id] │ │ │ └── index.js │ │ └── index.js │ └── hello.ts ├── comments │ ├── [pageid] │ │ ├── edit.js │ │ └── index.jsx │ └── index.jsx ├── index.tsx ├── infrastructure │ ├── [pageid].tsx │ └── index.tsx ├── programming │ ├── [pageid].tsx │ └── index.tsx ├── soft-skills │ ├── [pageid].tsx │ └── index.tsx └── test-works │ ├── [pageid].tsx │ └── index.tsx ├── playwright.config.ts ├── pnpm-lock.yaml ├── tests-examples └── demo-todo-app.spec.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | MONGODB_URI= 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@v3 9 | 10 | - name: Setup Node.js environment 11 | uses: actions/setup-node@v3.5.0 12 | with: 13 | node-version: 18 14 | 15 | - uses: pnpm/action-setup@v2.2.2 16 | with: 17 | version: 7.12.2 18 | 19 | - name: Install dependencies 20 | run: pnpm install --no-frozen-lockfile 21 | 22 | - name: Build 23 | run: pnpm run build 24 | env: 25 | MONGODB_URI: ${{ secrets.MONGODB_URI }} 26 | 27 | - name: Static HTML Export 28 | run: pnpm run export 29 | 30 | - name: Disable Jekyl # https://github.blog/2009-12-29-bypassing-jekyll-on-github-pages/ 31 | run: touch ./out/.nojekyll 32 | 33 | - name: Delpoy 🚀 34 | uses: JamesIves/github-pages-deploy-action@3.7.1 35 | with: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | BRANCH: gh-pages 38 | FOLDER: out 39 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | test: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 18 17 | 18 | - uses: pnpm/action-setup@v2.2.2 19 | with: 20 | version: 7.12.2 21 | 22 | - name: Install dependencies 23 | run: pnpm install --no-frozen-lockfile 24 | 25 | - name: Install Playwright Browsers 26 | run: npx playwright install --with-deps 27 | 28 | - name: Run Playwright tests 29 | run: npx playwright test 30 | 31 | - uses: actions/upload-artifact@v3 32 | if: always() 33 | with: 34 | name: playwright-report 35 | path: playwright-report/ 36 | retention-days: 30 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # editors 4 | .idea 5 | 6 | # dependencies 7 | node_modules 8 | /.pnp 9 | .pnp.js 10 | 11 | # testing 12 | /coverage 13 | 14 | # next.js 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | /build 20 | 21 | # misc 22 | .DS_Store 23 | *.pem 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | .pnpm-debug.log* 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | next-env.d.ts 40 | /test-results/ 41 | /playwright-report/ 42 | /playwright/.cache/ 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Easy Deep Learning 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # personal-learn-path 2 | Learn path constructor and knowledge base 3 | 4 | ## Tech stack 5 | - NextJS 6 | - MongoDB 7 | -------------------------------------------------------------------------------- /components/ContentEditor/ContentEditor.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { Editor, EditorState, convertFromRaw, RawDraftContentState, convertToRaw } from 'draft-js' 3 | import 'draft-js/dist/Draft.css' 4 | 5 | type ContentEditorType = { 6 | onSave: (content: object) => void 7 | } 8 | 9 | const ContentEditor = ({ onSave }: ContentEditorType) => { 10 | const [editorState, setEditorState] = useState(EditorState.createWithContent(emptyContentState)) 11 | 12 | return ( 13 |
14 | { 18 | console.log('data: ', data) 19 | const content = editorState.getCurrentContent(); 20 | console.log('content: ', convertToRaw(content)); 21 | }} 22 | /> 23 |
24 | ) 25 | } 26 | 27 | const emptyContentState = convertFromRaw({ 28 | entityMap: {}, 29 | blocks: [ 30 | { 31 | text: '', 32 | key: 'foo', 33 | type: 'unstyled', 34 | entityRanges: [], 35 | }, 36 | ], 37 | } as unknown as RawDraftContentState) 38 | 39 | export default ContentEditor 40 | -------------------------------------------------------------------------------- /components/ContentEditor/index.ts: -------------------------------------------------------------------------------- 1 | import ContentEditor from './ContentEditor' 2 | 3 | export { 4 | ContentEditor 5 | } 6 | -------------------------------------------------------------------------------- /components/Date/date.module.css: -------------------------------------------------------------------------------- 1 | .day {} 2 | 3 | .month {} 4 | 5 | .year {} 6 | -------------------------------------------------------------------------------- /components/Date/date.tsx: -------------------------------------------------------------------------------- 1 | import { parseISO, format } from 'date-fns' 2 | import { ru } from 'date-fns/locale' 3 | import dateStyles from './date.module.css' 4 | 5 | type DatePropsType = { 6 | dateString: string 7 | } 8 | 9 | export default function Date({ dateString }: DatePropsType) { 10 | const date = parseISO(dateString) 11 | return ( 12 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /components/Date/index.ts: -------------------------------------------------------------------------------- 1 | import Date from './date' 2 | 3 | export { Date } 4 | -------------------------------------------------------------------------------- /components/Footer/Footer.module.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | display: flex; 3 | } 4 | 5 | .footer__item { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Footer () { 4 | return ( 5 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /components/Footer/index.ts: -------------------------------------------------------------------------------- 1 | import Footer from './Footer' 2 | 3 | export { Footer } 4 | -------------------------------------------------------------------------------- /components/Form/CommentForm.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { useRouter } from 'next/router' 3 | import { mutate } from 'swr' 4 | 5 | const CommentForm = ({ formId, commentForm, forNewComment = true }) => { 6 | const router = useRouter() 7 | const contentType = 'application/json' 8 | const [errors, setErrors] = useState({}) 9 | const [message, setMessage] = useState('') 10 | 11 | const [form, setForm] = useState({ 12 | name: commentForm.name, 13 | email: commentForm.email, 14 | movie_id: commentForm.movie_id, 15 | text: commentForm.text, 16 | date: commentForm.date, 17 | }) 18 | 19 | /* The PUT method edits an existing entry in the mongodb database. */ 20 | const putData = async (form) => { 21 | const { id } = router.query 22 | 23 | try { 24 | const res = await fetch(`/api/comments/${id}`, { 25 | method: 'PUT', 26 | headers: { 27 | Accept: contentType, 28 | 'Content-Type': contentType, 29 | }, 30 | body: JSON.stringify(form), 31 | }) 32 | 33 | // Throw error with status code in case Fetch API req failed 34 | if (!res.ok) { 35 | throw new Error(res.status) 36 | } 37 | 38 | const { data } = await res.json() 39 | 40 | mutate(`/api/comments/${id}`, data, false) // Update the local data without a revalidation 41 | router.push('/') 42 | } catch (error) { 43 | setMessage('Failed to update comment') 44 | } 45 | } 46 | 47 | /* The POST method adds a new entry in the mongodb database. */ 48 | const postData = async (form) => { 49 | try { 50 | const res = await fetch('/api/comments', { 51 | method: 'POST', 52 | headers: { 53 | Accept: contentType, 54 | 'Content-Type': contentType, 55 | }, 56 | body: JSON.stringify(form), 57 | }) 58 | 59 | // Throw error with status code in case Fetch API req failed 60 | if (!res.ok) { 61 | throw new Error(res.status) 62 | } 63 | 64 | router.push('/') 65 | } catch (error) { 66 | setMessage('Failed to add comment') 67 | } 68 | } 69 | 70 | const handleChange = (e) => { 71 | const target = e.target 72 | const value = 73 | target.name === 'poddy_trained' ? target.checked : target.value 74 | const name = target.name 75 | 76 | setForm({ 77 | ...form, 78 | [name]: value, 79 | }) 80 | } 81 | 82 | /* Makes sure comment info is filled for comment name, owner name, species, and image url*/ 83 | const formValidate = () => { 84 | let err = {} 85 | if (!form.name) err.name = 'Name is required' 86 | if (!form.owner_name) err.owner_name = 'Owner is required' 87 | if (!form.species) err.species = 'Species is required' 88 | if (!form.image_url) err.image_url = 'Image URL is required' 89 | return err 90 | } 91 | 92 | const handleSubmit = (e) => { 93 | e.preventDefault() 94 | const errs = formValidate() 95 | if (Object.keys(errs).length === 0) { 96 | forNewComment ? postData(form) : putData(form) 97 | } else { 98 | setErrors({ errs }) 99 | } 100 | } 101 | 102 | return ( 103 | <> 104 |
105 |
106 | 107 | 115 |
116 | 117 |
118 | 119 | 127 |
128 | 129 |
130 | 131 | 139 |
140 | 141 |
142 | 143 |