├── public
├── robots.txt
├── manifest.json
└── favicon.svg
├── .prettierignore
├── src
├── components
│ ├── header
│ │ ├── header.css
│ │ └── header.tsx
│ ├── hello-message
│ │ ├── hello-message.css
│ │ └── hello-message.tsx
│ └── router-head
│ │ └── router-head.tsx
├── models
│ ├── lesson.ts
│ └── course.ts
├── global.css
├── routes
│ ├── layout.tsx
│ ├── head-links.tsx
│ ├── service-worker.ts
│ ├── index.tsx
│ ├── stores
│ │ └── index.tsx
│ └── resources
│ │ └── index.tsx
├── entry.dev.tsx
├── entry.preview.tsx
├── entry.ssr.tsx
└── root.tsx
├── backend
├── server.tsconfig.json
├── delete-course.route.ts
├── get-courses.route.ts
├── server.ts
├── search.route.ts
└── db-data.ts
├── vite.config.ts
├── .eslintignore
├── .gitignore
├── tsconfig.json
├── .eslintrc.cjs
├── package.json
└── README.md
/public/robots.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Files Prettier should not format
2 | **/*.log
3 | **/.DS_Store
4 | *.
5 | dist
6 | node_modules
7 |
--------------------------------------------------------------------------------
/src/components/header/header.css:
--------------------------------------------------------------------------------
1 | header {
2 | display: flex;
3 | }
4 |
5 | header ul {
6 | list-style: none;
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/backend/server.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "lib": ["es2017"]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/models/lesson.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface Lesson {
3 | id: number;
4 | description: string;
5 | duration: string;
6 | seqNo: number;
7 | courseId: number;
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/hello-message/hello-message.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | .hello-message {
4 | font-size: 30px;
5 | font-weight: bold;
6 | color:blue;
7 | }
8 |
9 | .hello-message.highlighted {
10 | color: orange;
11 | }
12 |
--------------------------------------------------------------------------------
/src/models/course.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export interface Course {
4 | id:string;
5 | description:string;
6 | iconUrl: string;
7 | courseListIcon: string;
8 | longDescription: string;
9 | category:string;
10 | lessonsCount:number;
11 | url:string;
12 | }
13 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/web-manifest-combined.json",
3 | "name": "qwik-project-name",
4 | "short_name": "Welcome to Qwik",
5 | "start_url": ".",
6 | "display": "standalone",
7 | "background_color": "#fff",
8 | "description": "A Qwik project app."
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/header/header.tsx:
--------------------------------------------------------------------------------
1 | import { component$, useStylesScoped$ } from '@builder.io/qwik';
2 | import styles from './header.css?inline';
3 | import { Link } from "@builder.io/qwik-city";
4 |
5 | export default component$(() => {
6 | useStylesScoped$(styles);
7 |
8 | return (
9 |
14 | );
15 | });
16 |
--------------------------------------------------------------------------------
/src/global.css:
--------------------------------------------------------------------------------
1 | /**
2 | * WHAT IS THIS FILE?
3 | *
4 | * Globally applied styles. No matter which components are in the page or matching route,
5 | * the styles in here will be applied to the Document, without any sort of CSS scoping.
6 | *
7 | */
8 |
9 | /* global font */
10 | h1, h2, h3, h3, h5, h6, span:not(.material-icons), div, ul, li, p {
11 | font-family: Inter, sans-serif;
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/layout.tsx:
--------------------------------------------------------------------------------
1 | import { component$, Slot } from '@builder.io/qwik';
2 | import Header from '../components/header/header';
3 |
4 | export default component$(() => {
5 | return (
6 | <>
7 |
8 |
9 |
12 |
13 |
16 | >
17 | );
18 | });
19 |
--------------------------------------------------------------------------------
/backend/delete-course.route.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | import {Request, Response} from 'express';
4 | import {COURSES} from "./db-data";
5 |
6 |
7 |
8 |
9 | export function deleteCourseById(req: Request, res: Response) {
10 |
11 | const courseId = req.params["id"];
12 |
13 | console.log(`Deleting course with id ${courseId}`);
14 |
15 | delete COURSES[courseId];
16 |
17 | res.status(200).json({ status: "OK" });
18 | }
19 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import { qwikVite } from '@builder.io/qwik/optimizer';
3 | import { qwikCity } from '@builder.io/qwik-city/vite';
4 | import tsconfigPaths from 'vite-tsconfig-paths';
5 |
6 | export default defineConfig(() => {
7 | return {
8 | plugins: [qwikCity(), qwikVite(), tsconfigPaths()],
9 | preview: {
10 | headers: {
11 | 'Cache-Control': 'public, max-age=600',
12 | },
13 | },
14 | };
15 | });
16 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.log
2 | **/.DS_Store
3 | *.
4 | .vscode/settings.json
5 | .history
6 | .yarn
7 | bazel-*
8 | bazel-bin
9 | bazel-out
10 | bazel-qwik
11 | bazel-testlogs
12 | dist
13 | dist-dev
14 | lib
15 | lib-types
16 | etc
17 | external
18 | node_modules
19 | temp
20 | tsc-out
21 | tsdoc-metadata.json
22 | target
23 | output
24 | rollup.config.js
25 | build
26 | .cache
27 | .vscode
28 | .rollup.cache
29 | dist
30 | tsconfig.tsbuildinfo
31 | vite.config.ts
32 | *.spec.tsx
33 | *.spec.ts
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build
2 | /dist
3 | /lib
4 | /lib-types
5 | /server
6 |
7 | # Development
8 | node_modules
9 |
10 | # Cache
11 | .cache
12 | .mf
13 | .vscode
14 | .rollup.cache
15 | tsconfig.tsbuildinfo
16 |
17 | # Logs
18 | logs
19 | *.log
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | pnpm-debug.log*
24 | lerna-debug.log*
25 |
26 | # Editor
27 | !.vscode/extensions.json
28 | .idea
29 | .DS_Store
30 | *.suo
31 | *.ntvs*
32 | *.njsproj
33 | *.sln
34 | *.sw?
35 |
36 | # Yarn
37 | .yarn/*
38 | !.yarn/releases
39 |
--------------------------------------------------------------------------------
/src/routes/head-links.tsx:
--------------------------------------------------------------------------------
1 | import { DocumentLink } from "@builder.io/qwik-city";
2 |
3 |
4 | export const commonLinks : DocumentLink[] = [
5 | {
6 | href: "https://fonts.googleapis.com",
7 | rel: "preconnect"
8 | },
9 | {
10 | href: "https://fonts.gstatic.com",
11 | rel: "preconnect",
12 | crossorigin: "true"
13 | },
14 | {
15 | href:"https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap",
16 | rel: "stylesheet"
17 | },
18 | {
19 | href:"https://fonts.googleapis.com/icon?family=Material+Icons",
20 | rel: "stylesheet"
21 | }
22 | ];
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/entry.dev.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * WHAT IS THIS FILE?
3 | *
4 | * Development entry point using only client-side modules:
5 | * - Do not use this mode in production!
6 | * - No SSR
7 | * - No portion of the application is pre-rendered on the server.
8 | * - All of the application is running eagerly in the browser.
9 | * - More code is transferred to the browser than in SSR mode.
10 | * - Optimizer/Serialization/Deserialization code is not exercised!
11 | */
12 | import { render, RenderOptions } from '@builder.io/qwik';
13 | import Root from './root';
14 |
15 | export default function (opts: RenderOptions) {
16 | return render(document, , opts);
17 | }
18 |
--------------------------------------------------------------------------------
/src/entry.preview.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * WHAT IS THIS FILE?
3 | *
4 | * It's the bundle entry point for `npm run preview`.
5 | * That is, serving your app built in production mode.
6 | *
7 | * Feel free to modify this file, but don't remove it!
8 | *
9 | * Learn more about Vite's preview command:
10 | * - https://vitejs.dev/config/preview-options.html#preview-options
11 | *
12 | */
13 | import { createQwikCity } from '@builder.io/qwik-city/middleware/node';
14 | import render from './entry.ssr';
15 | import qwikCityPlan from '@qwik-city-plan';
16 |
17 | /**
18 | * The default export is the QwikCity adaptor used by Vite preview.
19 | */
20 | export default createQwikCity({ render, qwikCityPlan });
21 |
--------------------------------------------------------------------------------
/backend/get-courses.route.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | import {Request, Response} from 'express';
4 | import {COURSES} from "./db-data";
5 |
6 |
7 |
8 | export function getAllCourses(req: Request, res: Response) {
9 |
10 | // throw "Error";
11 |
12 | setTimeout(() => {
13 |
14 | res.status(200).json(Object.values(COURSES).sort((c1:any, c2:any) => c1.seqNo - c2.seqNo));
15 |
16 | }, 2000);
17 |
18 |
19 | }
20 |
21 |
22 | export function getCourseById(req: Request, res: Response) {
23 |
24 | const courseId = req.params["id"];
25 |
26 | const courses:any = Object.values(COURSES);
27 |
28 | const course = courses.find(course => course.id == courseId);
29 |
30 | res.status(200).json(course);
31 | }
32 |
--------------------------------------------------------------------------------
/src/routes/service-worker.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * WHAT IS THIS FILE?
3 | *
4 | * The service-worker.ts file is used to have state of the art prefetching.
5 | * https://qwik.builder.io/qwikcity/prefetching/overview/
6 | *
7 | * Qwik uses a service worker to speed up your site and reduce latency, ie, not used in the traditional way of offline.
8 | * You can also use this file to add more functionality that runs in the service worker.
9 | */
10 | import { setupServiceWorker } from '@builder.io/qwik-city/service-worker';
11 |
12 | setupServiceWorker();
13 |
14 | addEventListener('install', () => self.skipWaiting());
15 |
16 | addEventListener('activate', () => self.clients.claim());
17 |
18 | declare const self: ServiceWorkerGlobalScope;
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "target": "ES2017",
5 | "module": "ES2020",
6 | "lib": ["es2020", "DOM", "WebWorker", "DOM.Iterable"],
7 | "jsx": "react-jsx",
8 | "jsxImportSource": "@builder.io/qwik",
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "resolveJsonModule": true,
12 | "moduleResolution": "node",
13 | "esModuleInterop": true,
14 | "skipLibCheck": true,
15 | "incremental": true,
16 | "isolatedModules": true,
17 | "outDir": "tmp",
18 | "noEmit": true,
19 | "types": ["node", "vite/client"],
20 | "paths": {
21 | "~/*": ["./src/*"]
22 | }
23 | },
24 | "files": ["./.eslintrc.cjs"],
25 | "include": ["src"]
26 | }
27 |
--------------------------------------------------------------------------------
/src/entry.ssr.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * WHAT IS THIS FILE?
3 | *
4 | * SSR entry point, in all cases the application is render outside the browser, this
5 | * entry point will be the common one.
6 | *
7 | * - Server (express, cloudflare...)
8 | * - npm run start
9 | * - npm run preview
10 | * - npm run build
11 | *
12 | */
13 | import { renderToStream, RenderToStreamOptions } from '@builder.io/qwik/server';
14 | import { manifest } from '@qwik-client-manifest';
15 | import Root from './root';
16 |
17 | export default function (opts: RenderToStreamOptions) {
18 | return renderToStream(, {
19 | manifest,
20 | ...opts,
21 | // Use container attributes to set attributes on the html tag.
22 | containerAttributes: {
23 | lang: 'en-us',
24 | ...opts.containerAttributes,
25 | },
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/server.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 | import {Application} from "express";
3 | import {getAllCourses, getCourseById} from "./get-courses.route";
4 | import {searchLessons} from "./search.route";
5 | import { deleteCourseById } from "./delete-course.route";
6 |
7 |
8 | const app: Application = express();
9 |
10 | const cors = require('cors');
11 |
12 | app.use(cors({origin: true}));
13 |
14 | app.route('/').get((req, res) => {
15 | res.status(200).send(`
Server is up and running.
`);
16 | });
17 |
18 | app.route('/api/courses').get(getAllCourses);
19 |
20 | app.route('/api/courses/:id').get(getCourseById);
21 |
22 | app.route('/api/courses/:id').delete(deleteCourseById);
23 |
24 | app.route('/api/lessons').get(searchLessons);
25 |
26 |
27 |
28 |
29 | const httpServer:any = app.listen(9000, () => {
30 | console.log("HTTP REST API Server running at http://localhost:" + httpServer.address().port);
31 | });
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import { component$, $ } from "@builder.io/qwik";
2 | import { HelloMessage } from "~/components/hello-message/hello-message";
3 |
4 |
5 | export default component$(() => {
6 |
7 | console.log(`Initializing Hello World component.`);
8 |
9 | const sayHello = $(() => {
10 | alert("Hello World!");
11 | });
12 |
13 | const messages = [
14 | "Hello World",
15 | "Welcome to this Qwik Course",
16 | "Learn the Qwik Framework!"
17 | ];
18 |
19 | const onShowMessageClicked = $((message:string) => alert(message));
20 |
21 | return (
22 | <>
23 |
24 | { messages.map((message, index) => (
25 |
27 | ))
28 |
29 | }
30 |
31 |
32 |
33 | >
34 | );
35 |
36 | });
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/router-head/router-head.tsx:
--------------------------------------------------------------------------------
1 | import { component$ } from '@builder.io/qwik';
2 | import { useDocumentHead, useLocation } from '@builder.io/qwik-city';
3 |
4 | /**
5 | * The RouterHead component is placed inside of the document `` element.
6 | */
7 | export const RouterHead = component$(() => {
8 | const head = useDocumentHead();
9 | const loc = useLocation();
10 |
11 | return (
12 | <>
13 | {head.title}
14 |
15 |
16 |
17 |
18 |
19 | {head.meta.map((m) => (
20 |
21 | ))}
22 |
23 | {head.links.map((l) => (
24 |
25 | ))}
26 |
27 | {head.styles.map((s) => (
28 |
29 | ))}
30 | >
31 | );
32 | });
33 |
--------------------------------------------------------------------------------
/src/root.tsx:
--------------------------------------------------------------------------------
1 | import { component$, createContext, useContextProvider, useStore, useStyles$ } from "@builder.io/qwik";
2 | import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city';
3 | import { RouterHead } from './components/router-head/router-head';
4 |
5 | import globalStyles from './global.css?inline';
6 |
7 |
8 |
9 |
10 | export default component$(() => {
11 | /**
12 | * The root of a QwikCity site always start with the component,
13 | * immediately followed by the document's and .
14 | *
15 | * Dont remove the `` and `` elements.
16 | */
17 | useStyles$(globalStyles);
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | });
33 |
--------------------------------------------------------------------------------
/src/routes/stores/index.tsx:
--------------------------------------------------------------------------------
1 | import { component$, createContext, useContext, useContextProvider, useStore } from "@builder.io/qwik";
2 |
3 | interface MessagesStore {
4 | messages: string[],
5 | index: number
6 | }
7 |
8 | export const MessagesContext = createContext("MESSAGES");
9 |
10 | export default component$(() => {
11 |
12 | const messages = [
13 | "Hello World",
14 | "Welcome to this Qwik Course",
15 | "Learn the Qwik Framework!"
16 | ];
17 |
18 | const store = useStore({
19 | messages,
20 | index: 0
21 | });
22 |
23 | useContextProvider(MessagesContext, store)
24 |
25 | return (
26 | <>
27 | Qwik Stores:
28 |
29 |
30 |
31 |
32 | >
33 | )
34 | })
35 |
36 |
37 | export const Message = component$(() => {
38 |
39 | const store = useContext(MessagesContext);
40 |
41 | const {messages, index} = store;
42 |
43 | return (
44 | {messages[index]}
45 | );
46 | });
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/routes/resources/index.tsx:
--------------------------------------------------------------------------------
1 | import { component$, useResource$, Resource, $, useStore } from "@builder.io/qwik";
2 | import { Course } from "~/models/course";
3 |
4 | export default component$(() => {
5 |
6 | const store = useStore({
7 | reloadCounter: 0
8 | });
9 |
10 | const resource = useResource$((ctx) => {
11 | ctx.track(() => store.reloadCounter);
12 | return getCourses();
13 | });
14 |
15 | return (
16 | <>
17 |
18 |
19 |
20 | (
23 | Loading ...
24 | )}
25 | onRejected={() => (
26 | Request failed.
27 | )}
28 | onResolved={courses => (
29 | <>
30 | {
31 | courses.map(course => (
32 | {course.description}
33 | ))
34 | }
35 | >
36 | )} />
37 | >
38 | )
39 | })
40 |
41 | export async function getCourses() {
42 | const response = await fetch(`http://localhost:9000/api/courses`);
43 | return response.json();
44 | }
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/components/hello-message/hello-message.tsx:
--------------------------------------------------------------------------------
1 | import { component$, PropFunction, useStylesScoped$ } from "@builder.io/qwik";
2 |
3 | import styles from './hello-message.css?inline';
4 |
5 | interface HelloMessageProps {
6 | message:string,
7 | courseVersion?:number,
8 | showButton: boolean,
9 | onShowMessage: PropFunction<(message:string) => void>;
10 | }
11 |
12 | export const HelloMessage = component$((props: HelloMessageProps) => {
13 |
14 | useStylesScoped$(styles);
15 |
16 | const {message, courseVersion, onShowMessage, showButton} = props;
17 |
18 | const cssClasses = ['hello-message'];
19 |
20 | if (courseVersion == 1) {
21 | cssClasses.push('highlighted');
22 | }
23 |
24 | const customStyles = (courseVersion == 2) ? {
25 | color: 'red',
26 | 'text-decoration': 'underline'
27 | } : {};
28 |
29 | return (
30 |
31 | {
32 | <>
33 |
34 |
{message}: version {courseVersion}
35 |
36 | {showButton && (
37 |
38 | )}
39 |
40 | >
41 | }
42 |
43 | );
44 | });
45 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | es2021: true,
6 | node: true,
7 | },
8 | extends: [
9 | 'eslint:recommended',
10 | 'plugin:@typescript-eslint/recommended',
11 | 'plugin:qwik/recommended',
12 | ],
13 | parser: '@typescript-eslint/parser',
14 | parserOptions: {
15 | tsconfigRootDir: __dirname,
16 | project: ['./tsconfig.json'],
17 | ecmaVersion: 2021,
18 | sourceType: 'module',
19 | ecmaFeatures: {
20 | jsx: true,
21 | },
22 | },
23 | plugins: ['@typescript-eslint'],
24 | rules: {
25 | '@typescript-eslint/no-explicit-any': 'off',
26 | '@typescript-eslint/explicit-module-boundary-types': 'off',
27 | '@typescript-eslint/no-inferrable-types': 'off',
28 | '@typescript-eslint/no-non-null-assertion': 'off',
29 | '@typescript-eslint/no-empty-interface': 'off',
30 | '@typescript-eslint/no-namespace': 'off',
31 | '@typescript-eslint/no-empty-function': 'off',
32 | '@typescript-eslint/no-this-alias': 'off',
33 | '@typescript-eslint/ban-types': 'off',
34 | '@typescript-eslint/ban-ts-comment': 'off',
35 | 'prefer-spread': 'off',
36 | 'no-case-declarations': 'off',
37 | 'no-console': 'off',
38 | '@typescript-eslint/no-unused-vars': ['error'],
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/backend/search.route.ts:
--------------------------------------------------------------------------------
1 |
2 | import {Request, Response} from 'express';
3 | import {LESSONS} from "./db-data";
4 | import {setTimeout} from "timers";
5 |
6 | export function searchLessons(req: Request, res: Response) {
7 |
8 | const queryParams = req.query as any;
9 |
10 | const courseId = queryParams.courseId,
11 | filter = queryParams.filter || '',
12 | sortOrder = queryParams.sortOrder,
13 | pageNumber = parseInt(queryParams.pageNumber) || 0,
14 | pageSize = parseInt(queryParams.pageSize),
15 | sortColumn = queryParams.sortColumn ?? "seqNo";
16 |
17 | let lessons = Object.values(LESSONS)
18 | .filter(lesson => lesson.courseId == courseId)
19 | .sort((l1, l2) => {
20 | if (l1[sortColumn] > l2[sortColumn]) {
21 | return 1;
22 | }
23 | else if (l1[sortColumn] < l2[sortColumn]) {
24 | return -1;
25 | }
26 | else {
27 | return 0;
28 | }
29 | });
30 |
31 | if (filter) {
32 | lessons = lessons.filter(lesson => lesson.description.trim().toLowerCase().search(filter.toLowerCase()) >= 0);
33 | }
34 |
35 | if (sortOrder == "desc") {
36 | lessons = lessons.reverse();
37 | }
38 |
39 | const initialPos = pageNumber * pageSize;
40 |
41 | const lessonsPage = lessons.slice(initialPos, initialPos + pageSize);
42 |
43 | setTimeout(() => {
44 | res.status(200).json({payload: lessonsPage});
45 | },1000);
46 |
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "qwik-course",
3 | "description": "App with Routing built-in (recommended)",
4 | "engines": {
5 | "node": ">=15.0.0"
6 | },
7 | "private": true,
8 | "scripts": {
9 | "server": "ts-node -P ./backend/server.tsconfig.json ./backend/server.ts",
10 | "build": "qwik build",
11 | "build.client": "vite build",
12 | "build.preview": "vite build --ssr src/entry.preview.tsx",
13 | "build.types": "tsc --incremental --noEmit",
14 | "deploy": "echo 'Run \"npm run qwik add\" to install a server adaptor'",
15 | "dev": "vite --mode ssr",
16 | "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
17 | "fmt": "prettier --write .",
18 | "fmt.check": "prettier --check .",
19 | "lint": "eslint \"src/**/*.ts*\"",
20 | "preview": "qwik build preview && vite preview --open",
21 | "start": "vite --open --mode ssr",
22 | "qwik": "qwik"
23 | },
24 | "devDependencies": {
25 | "@builder.io/qwik": "0.16.1",
26 | "@builder.io/qwik-city": "0.0.128",
27 | "@types/eslint": "8.4.10",
28 | "@types/node": "latest",
29 | "@typescript-eslint/eslint-plugin": "5.45.0",
30 | "@typescript-eslint/parser": "5.45.0",
31 | "autoprefixer": "10.4.11",
32 | "eslint": "8.28.0",
33 | "eslint-plugin-qwik": "0.15.2",
34 | "node-fetch": "3.3.0",
35 | "prettier": "2.8.0",
36 | "ts-node": "^10.9.1",
37 | "typescript": "4.9.3",
38 | "vite": "3.2.4",
39 | "vite-tsconfig-paths": "3.5.0"
40 | },
41 | "dependencies": {
42 | "@types/express": "^4.17.15",
43 | "cors": "^2.8.5",
44 | "express": "^4.18.2",
45 | "material-components-web": "^14.0.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Qwik For Beginners Course
3 |
4 | This repository contains the code of the [Qwik For Beginners Course](https://qwikacademy.io/courses/qwik-course):
5 |
6 | 
7 |
8 | IMPORTANT: please use Node 18 (Long-term support version) to take this course.
9 |
10 | # Instructions for taking this course
11 |
12 | If you want, you can install the course code and code along as you go through the course.
13 |
14 | Notice that this is optional, the course will still make perfect sense if you choose to just watch it and not code along as you go.
15 |
16 | But coding along is one of the best ways to learn, so I encourage you to try it out.
17 |
18 | This section teaches you how to get your local development environment up and running, so that you can comfortably learn the framework in a stable environment.
19 |
20 | This repository provides you with a playground environment with everything you need, including a backend server that you will use during the course.
21 |
22 | This repository contains a few branches:
23 |
24 | - the 1-start branch is the starting point of the course
25 |
26 | - the main branch contains the finished version of the code. You should refer to this branch in case of doubts with any of the imports, and to compare your code with the finished code.
27 |
28 | # How To install the course code
29 |
30 | You can find the course repository in the following url:
31 |
32 | https://github.com/qwik-courses/qwik-course
33 |
34 | Start by cloning the repository:
35 |
36 | git clone https://github.com/qwik-courses/qwik-course.git
37 | cd qwik-course
38 |
39 | Then switch to the 1-start branch:
40 |
41 | git checkout 1-start
42 |
43 | Install your dependencies:
44 |
45 | npm install
46 |
47 | # To Run the Development Backend Server
48 |
49 | We can start the sample application backend with the following command:
50 |
51 | npm run server
52 |
53 | This is a small Node REST API server.
54 |
55 | # To run the Development UI Server
56 |
57 | To run the frontend part of our code, run the following command:
58 |
59 | npm start
60 |
61 | # Other Courses
62 |
63 | Here are some other courses from the same author ([Vasco Cavalheiro](https://www.linkedin.com/in/vascocavalheiro/)).
64 |
65 | # Angular Forms In Depth
66 |
67 | If you are looking for the [Angular Forms In Depth](https://angular-university.io/course/angular-forms-course) course, the repo with the full code can be found here:
68 |
69 | 
70 |
71 | # Angular Router In Depth
72 |
73 | If you are looking for the [Angular Router In Depth](https://angular-university.io/course/angular-router-course) course, the repo with the full code can be found here:
74 |
75 | 
76 |
77 | # NgRx (with NgRx Data) - The Complete Guide
78 |
79 | If you are looking for the [Ngrx (with NgRx Data) - The Complete Guide](https://angular-university.io/course/ngrx-course), the repo with the full code can be found here:
80 |
81 | 
82 |
83 |
84 | # Angular Core Deep Dive Course
85 |
86 | If you are looking for the [Angular Core Deep Dive Course](https://angular-university.io/course/angular-course), the repo with the full code can be found here:
87 |
88 | 
89 |
90 | # RxJs In Practice
91 |
92 | If you are looking for the [RxJs In Practice](https://angular-university.io/course/rxjs-course), the repo with the full code can be found here:
93 |
94 | 
95 |
96 | # NestJs In Practice (with MongoDB)
97 |
98 | If you are looking for the [NestJs In Practice Course](https://angular-university.io/course/nestjs-course), the repo with the full code can be found here:
99 |
100 | 
101 |
102 | # Angular Testing Course
103 |
104 | If you are looking for the [Angular Testing Course](https://angular-university.io/course/angular-testing-course), the repo with the full code can be found here:
105 |
106 | 
107 |
108 | # Serverless Angular with Firebase Course
109 |
110 | If you are looking for the [Serverless Angular with Firebase Course](https://angular-university.io/course/firebase-course), the repo with the full code can be found here:
111 |
112 | 
113 |
114 | # Angular Universal Course
115 |
116 | If you are looking for the [Angular Universal Course](https://angular-university.io/course/angular-universal-course), the repo with the full code can be found here:
117 |
118 | 
119 |
120 | # Angular PWA Course
121 |
122 | If you are looking for the [Angular PWA Course](https://angular-university.io/course/angular-pwa-course), the repo with the full code can be found here:
123 |
124 | 
125 |
126 | # Angular Security Masterclass
127 |
128 | If you are looking for the [Angular Security Masterclass](https://angular-university.io/course/angular-security-course), the repo with the full code can be found here:
129 |
130 | [Angular Security Masterclass](https://github.com/angular-university/angular-security-course).
131 |
132 | 
133 |
134 | # Angular Advanced Library Laboratory Course
135 |
136 | If you are looking for the Angular Advanced Course, the repo with the full code can be found here:
137 |
138 | [Angular Advanced Library Laboratory Course: Build Your Own Library](https://angular-university.io/course/angular-advanced-course).
139 |
140 | 
141 |
142 |
143 | ## RxJs and Reactive Patterns Angular Architecture Course
144 |
145 | If you are looking for the RxJs and Reactive Patterns Angular Architecture Course code, the repo with the full code can be found here:
146 |
147 | [RxJs and Reactive Patterns Angular Architecture Course](https://angular-university.io/course/reactive-angular-architecture-course)
148 |
149 | 
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
--------------------------------------------------------------------------------
/backend/db-data.ts:
--------------------------------------------------------------------------------
1 | export const COURSES: any = {
2 |
3 | 11: {
4 | id: 11,
5 | description: 'Angular Material Course',
6 | longDescription: 'Build Applications with the official Angular UI Widget Library',
7 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-material-course-1.jpg',
8 | category: 'BEGINNER',
9 | seqNo: 0,
10 | url: 'angular-material-course',
11 | price: 50,
12 | lessonsCount: 11,
13 | },
14 |
15 | 19: {
16 | id: 19,
17 | description: 'Angular Forms In Depth',
18 | longDescription: 'Build complex enterprise data forms with the powerful Angular Forms module',
19 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-forms-course-small.jpg',
20 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png',
21 | category: 'BEGINNER',
22 | lessonsCount: 10,
23 | seqNo: 1,
24 | url: 'angular-forms-course',
25 | price: 50
26 | },
27 |
28 |
29 | 18: {
30 | id: 18,
31 | description: 'Angular Router In Depth',
32 | longDescription: 'Build large-scale Single Page Applications with the powerful Angular Router',
33 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-router-course.jpg',
34 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png',
35 | category: 'BEGINNER',
36 | lessonsCount: 10,
37 | seqNo: 2,
38 | url: 'angular-router-course',
39 | price: 50
40 | },
41 |
42 | 17: {
43 | id: 17,
44 | description: 'Reactive Angular Course',
45 | longDescription: 'How to build Angular applications in Reactive style using plain RxJs - Patterns and Anti-Patterns',
46 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/reactive-angular-course.jpg',
47 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png',
48 | category: 'BEGINNER',
49 | lessonsCount: 10,
50 | seqNo: 3,
51 | url: 'reactive-angular-course',
52 | price: 50
53 |
54 | },
55 | 3: {
56 | id: 3,
57 | description: 'RxJs In Practice Course',
58 | longDescription: 'Understand the RxJs Observable pattern, learn the RxJs Operators via practical examples',
59 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/rxjs-in-practice-course.png',
60 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png',
61 | category: 'BEGINNER',
62 | lessonsCount: 10,
63 | seqNo: 4,
64 | url: 'rxjs-course',
65 | price: 50
66 | },
67 |
68 | 4: {
69 | id: 4,
70 | description: 'NgRx (with NgRx Data) - The Complete Guide',
71 | longDescription: 'Learn the modern Ngrx Ecosystem, including NgRx Data, Store, Effects, Router Store, Ngrx Entity, and Dev Tools.',
72 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/ngrx-v2.png',
73 | category: 'BEGINNER',
74 | lessonsCount: 10,
75 | seqNo: 5,
76 | url: 'ngrx-course',
77 | promo: false,
78 | price: 50
79 | },
80 |
81 |
82 | 2: {
83 | id: 2,
84 | description: 'Angular Core Deep Dive',
85 | longDescription: 'A detailed walk-through of the most important part of Angular - the Core and Common modules',
86 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-core-in-depth-small.png',
87 | lessonsCount: 10,
88 | category: 'BEGINNER',
89 | seqNo: 6,
90 | url: 'angular-core-course',
91 | price: 50
92 | },
93 |
94 |
95 | 5: {
96 | id: 5,
97 |
98 | description: 'Angular for Beginners',
99 | longDescription: 'Establish a solid layer of fundamentals, learn what\'s under the hood of Angular',
100 | iconUrl: 'https://angular-academy.s3.amazonaws.com/thumbnails/angular2-for-beginners-small-v2.png',
101 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png',
102 | category: 'BEGINNER',
103 | lessonsCount: 10,
104 | seqNo: 7,
105 | url: 'angular-for-beginners',
106 | price: 50
107 | },
108 |
109 | 12: {
110 | id: 12,
111 | description: 'Angular Testing Course',
112 | longDescription: 'In-depth guide to Unit Testing and E2E Testing of Angular Applications',
113 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-testing-small.png',
114 | category: 'BEGINNER',
115 | seqNo: 8,
116 | url: 'angular-testing-course',
117 | lessonsCount: 10,
118 | promo: false,
119 | price: 50
120 | },
121 |
122 |
123 | 1: {
124 | id: 1,
125 | description: 'Serverless Angular with Firebase Course',
126 | longDescription: 'Serveless Angular with Firestore, Firebase Storage & Hosting, Firebase Cloud Functions & AngularFire',
127 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/serverless-angular-small.png',
128 | lessonsCount: 10,
129 | category: 'BEGINNER',
130 | seqNo: 9,
131 | url: 'serverless-angular',
132 | price: 50
133 | },
134 |
135 | 16: {
136 | id: 16,
137 | description: 'Stripe Payments In Practice',
138 | longDescription: 'Build your own ecommerce store & membership website with Firebase, Stripe and Express',
139 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/stripe-course.jpg',
140 | lessonsCount: 10,
141 | category: 'BEGINNER',
142 | seqNo: 10,
143 | url: 'stripe-course',
144 | price: 50
145 | },
146 |
147 |
148 | 14: {
149 | id: 14,
150 | description: 'NestJs In Practice (with MongoDB)',
151 | longDescription: 'Build a modern REST backend using Typescript, MongoDB and the familiar Angular API.',
152 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/nestjs-v2.png',
153 | category: 'BEGINNER',
154 | lessonsCount: 10,
155 | seqNo: 11,
156 | url: 'nestjs-course',
157 | promo: false,
158 | price: 50
159 | },
160 |
161 |
162 | 6: {
163 | id: 6,
164 | description: 'Angular Security Course - Web Security Fundamentals',
165 | longDescription: 'Learn Web Security Fundamentals and apply them to defend an Angular / Node Application from multiple types of attacks.',
166 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/security-cover-small-v2.png',
167 | courseListIcon: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/lock-v2.png',
168 | category: 'ADVANCED',
169 | lessonsCount: 11,
170 | seqNo: 12,
171 | url: 'angular-security-course',
172 | price: 50
173 | },
174 |
175 | 7: {
176 | id: 7,
177 | description: 'Angular PWA - Progressive Web Apps Course',
178 | longDescription: 'Learn Angular Progressive Web Applications, build the future of the Web Today.',
179 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-pwa-course.png',
180 | courseListIcon: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/alien.png',
181 | category: 'ADVANCED',
182 | lessonsCount: 8,
183 | seqNo: 14,
184 | url: 'angular-pwa-course',
185 | price: 50
186 | },
187 |
188 | 8: {
189 | id: 8,
190 | description: 'Angular Advanced Library Laboratory: Build Your Own Library',
191 | longDescription: 'Learn Advanced Angular functionality typically used in Library Development. Advanced Components, Directives, Testing, Npm',
192 | iconUrl: 'https://angular-academy.s3.amazonaws.com/thumbnails/advanced_angular-small-v3.png',
193 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/thumbnails/angular-advanced-lesson-icon.png',
194 | category: 'ADVANCED',
195 | seqNo: 15,
196 | url: 'angular-advanced-course',
197 | price: 50
198 | },
199 |
200 | 9: {
201 | id: 9,
202 | description: 'The Complete Typescript Course',
203 | longDescription: 'Complete Guide to Typescript From Scratch: Learn the language in-depth and use it to build a Node REST API.',
204 | iconUrl: 'https://angular-academy.s3.amazonaws.com/thumbnails/typescript-2-small.png',
205 | courseListIcon: 'https://angular-academy.s3.amazonaws.com/thumbnails/typescript-2-lesson.png',
206 | category: 'BEGINNER',
207 | seqNo: 16,
208 | url: 'typescript-course',
209 | price: 50
210 | },
211 |
212 | };
213 |
214 | export const LESSONS = {
215 |
216 | 1: {
217 | id: 1,
218 | 'description': 'Angular Tutorial For Beginners - Build Your First App - Hello World Step By Step',
219 | 'duration': '4:17',
220 | 'seqNo': 1,
221 | courseId: 5
222 | },
223 | 2: {
224 | id: 2,
225 | 'description': 'Building Your First Component - Component Composition',
226 | 'duration': '2:07',
227 | 'seqNo': 2,
228 | courseId: 5
229 | },
230 | 3: {
231 | id: 3,
232 | 'description': 'Component @Input - How To Pass Input Data To an Component',
233 | 'duration': '2:33',
234 | 'seqNo': 3,
235 | courseId: 5
236 | },
237 | 4: {
238 | id: 4,
239 | 'description': ' Component Events - Using @Output to create custom events',
240 | 'duration': '4:44',
241 | 'seqNo': 4,
242 | courseId: 5
243 | },
244 | 5: {
245 | id: 5,
246 | 'description': ' Component Templates - Inline Vs External',
247 | 'duration': '2:55',
248 | 'seqNo': 5,
249 | courseId: 5
250 | },
251 | 6: {
252 | id: 6,
253 | 'description': 'Styling Components - Learn About Component Style Isolation',
254 | 'duration': '3:27',
255 | 'seqNo': 6,
256 | courseId: 5
257 | },
258 | 7: {
259 | id: 7,
260 | 'description': ' Component Interaction - Extended Components Example',
261 | 'duration': '9:22',
262 | 'seqNo': 7,
263 | courseId: 5
264 | },
265 | 8: {
266 | id: 8,
267 | 'description': ' Components Tutorial For Beginners - Components Exercise !',
268 | 'duration': '1:26',
269 | 'seqNo': 8,
270 | courseId: 5
271 | },
272 | 9: {
273 | id: 9,
274 | 'description': ' Components Tutorial For Beginners - Components Exercise Solution Inside',
275 | 'duration': '2:08',
276 | 'seqNo': 9,
277 | courseId: 5
278 | },
279 | 10: {
280 | id: 10,
281 | 'description': ' Directives - Inputs, Output Event Emitters and How To Export Template References',
282 | 'duration': '4:01',
283 | 'seqNo': 10,
284 | courseId: 5
285 | },
286 |
287 |
288 | // Security Course
289 | 11: {
290 | id: 11,
291 | 'description': 'Course Helicopter View',
292 | 'duration': '08:19',
293 | 'seqNo': 1,
294 | courseId: 6
295 | },
296 |
297 | 12: {
298 | id: 12,
299 | 'description': 'Installing Git, Node, NPM and Choosing an IDE',
300 | 'duration': '04:17',
301 | 'seqNo': 2,
302 | courseId: 6
303 | },
304 |
305 | 13: {
306 | id: 13,
307 | 'description': 'Installing The Lessons Code - Learn Why Its Essential To Use NPM 5',
308 | 'duration': '06:05',
309 | 'seqNo': 3,
310 | courseId: 6
311 | },
312 |
313 | 14: {
314 | id: 14,
315 | 'description': 'How To Run Node In TypeScript With Hot Reloading',
316 | 'duration': '03:57',
317 | 'seqNo': 4,
318 | courseId: 6
319 | },
320 |
321 | 15: {
322 | id: 15,
323 | 'description': 'Guided Tour Of The Sample Application',
324 | 'duration': '06:00',
325 | 'seqNo': 5,
326 | courseId: 6
327 | },
328 | 16: {
329 | id: 16,
330 | 'description': 'Client Side Authentication Service - API Design',
331 | 'duration': '04:53',
332 | 'seqNo': 6,
333 | courseId: 6
334 | },
335 | 17: {
336 | id: 17,
337 | 'description': 'Client Authentication Service - Design and Implementation',
338 | 'duration': '09:14',
339 | 'seqNo': 7,
340 | courseId: 6
341 | },
342 | 18: {
343 | id: 18,
344 | 'description': 'The New Angular HTTP Client - Doing a POST Call To The Server',
345 | 'duration': '06:08',
346 | 'seqNo': 8,
347 | courseId: 6
348 | },
349 | 19: {
350 | id: 19,
351 | 'description': 'User Sign Up Server-Side Implementation in Express',
352 | 'duration': '08:50',
353 | 'seqNo': 9,
354 | courseId: 6
355 | },
356 | 20: {
357 | id: 20,
358 | 'description': 'Introduction To Cryptographic Hashes - A Running Demo',
359 | 'duration': '05:46',
360 | 'seqNo': 10,
361 | courseId: 6
362 | },
363 | 21: {
364 | id: 21,
365 | 'description': 'Some Interesting Properties Of Hashing Functions - Validating Passwords',
366 | 'duration': '06:31',
367 | 'seqNo': 11,
368 | courseId: 6
369 | },
370 |
371 |
372 | // PWA [courseUrl]
373 |
374 | 22: {
375 | id: 22,
376 | 'description': 'Course Kick-Off - Install Node, NPM, IDE And Service Workers Section Code',
377 | 'duration': '07:19',
378 | 'seqNo': 1,
379 | courseId: 7
380 | },
381 | 23: {
382 | id: 23,
383 | 'description': 'Service Workers In a Nutshell - Service Worker Registration',
384 | 'duration': '6:59',
385 | 'seqNo': 2,
386 | courseId: 7
387 | },
388 | 24: {
389 | id: 24,
390 | 'description': 'Service Workers Hello World - Lifecycle Part 1 and PWA Chrome Dev Tools',
391 | 'duration': '7:28',
392 | 'seqNo': 3,
393 | courseId: 7
394 | },
395 | 25: {
396 | id: 25,
397 | 'description': 'Service Workers and Application Versioning - Install & Activate Lifecycle Phases',
398 | 'duration': '10:17',
399 | 'seqNo': 4,
400 | courseId: 7
401 | },
402 |
403 | 26: {
404 | id: 26,
405 | 'description': 'Downloading The Offline Page - The Service Worker Installation Phase',
406 | 'duration': '09:50',
407 | 'seqNo': 5,
408 | courseId: 7
409 | },
410 | 27: {
411 | id: 27,
412 | 'description': 'Introduction to the Cache Storage PWA API',
413 | 'duration': '04:44',
414 | 'seqNo': 6,
415 | courseId: 7
416 | },
417 | 28: {
418 | id: 28,
419 | 'description': 'View Service Workers HTTP Interception Features In Action',
420 | 'duration': '06:07',
421 | 'seqNo': 7,
422 | courseId: 7
423 | },
424 | 29: {
425 | id: 29,
426 | 'description': 'Service Workers Error Handling - Serving The Offline Page',
427 | 'duration': '5:38',
428 | 'seqNo': 8,
429 | courseId: 7
430 | },
431 |
432 | // Serverless Angular with Firebase Course
433 |
434 | 30: {
435 | id: 30,
436 | description: 'Development Environment Setup',
437 | 'duration': '5:38',
438 | 'seqNo': 1,
439 | courseId: 1
440 | },
441 |
442 | 31: {
443 | id: 31,
444 | description: 'Introduction to the Firebase Ecosystem',
445 | 'duration': '5:12',
446 | 'seqNo': 2,
447 | courseId: 1
448 | },
449 |
450 | 32: {
451 | id: 32,
452 | description: 'Importing Data into Firestore',
453 | 'duration': '4:07',
454 | 'seqNo': 3,
455 | courseId: 1
456 | },
457 |
458 | 33: {
459 | id: 33,
460 | description: 'Firestore Documents in Detail',
461 | 'duration': '7:32',
462 | 'seqNo': 4,
463 | courseId: 1
464 | },
465 |
466 | 34: {
467 | id: 34,
468 | description: 'Firestore Collections in Detail',
469 | 'duration': '6:28',
470 | 'seqNo': 5,
471 | courseId: 1
472 | },
473 |
474 | 35: {
475 | id: 35,
476 | description: 'Firestore Unique Identifiers',
477 | 'duration': '4:38',
478 | 'seqNo': 6,
479 | courseId: 1
480 | },
481 |
482 | 36: {
483 | id: 36,
484 | description: 'Querying Firestore Collections',
485 | 'duration': '7:54',
486 | 'seqNo': 7,
487 | courseId: 1
488 | },
489 |
490 | 37: {
491 | id: 37,
492 | description: 'Firebase Security Rules In Detail',
493 | 'duration': '5:31',
494 | 'seqNo': 8,
495 | courseId: 1
496 | },
497 |
498 | 38: {
499 | id: 38,
500 | description: 'Firebase Cloud Functions In Detail',
501 | 'duration': '8:19',
502 | 'seqNo': 9,
503 | courseId: 1
504 | },
505 |
506 | 39: {
507 | id: 39,
508 | description: 'Firebase Storage In Detail',
509 | 'duration': '7:05',
510 | 'seqNo': 10,
511 | courseId: 1
512 | },
513 |
514 |
515 | // Angular Testing Course
516 |
517 | 40: {
518 | id: 40,
519 | description: 'Angular Testing Course - Helicopter View',
520 | 'duration': '5:38',
521 | 'seqNo': 1,
522 | courseId: 12
523 | },
524 |
525 | 41: {
526 | id: 41,
527 | description: 'Setting Up the Development Environment',
528 | 'duration': '5:12',
529 | 'seqNo': 2,
530 | courseId: 12
531 | },
532 |
533 | 42: {
534 | id: 42,
535 | description: 'Introduction to Jasmine, Spies and specs',
536 | 'duration': '4:07',
537 | 'seqNo': 3,
538 | courseId: 12
539 | },
540 |
541 | 43: {
542 | id: 43,
543 | description: 'Introduction to Service Testing',
544 | 'duration': '7:32',
545 | 'seqNo': 4,
546 | courseId: 12
547 | },
548 |
549 | 44: {
550 | id: 44,
551 | description: 'Settting up the Angular TestBed',
552 | 'duration': '6:28',
553 | 'seqNo': 5,
554 | courseId: 12
555 | },
556 |
557 | 45: {
558 | id: 45,
559 | description: 'Mocking Angular HTTP requests',
560 | 'duration': '4:38',
561 | 'seqNo': 6,
562 | courseId: 12
563 | },
564 |
565 | 46: {
566 | id: 46,
567 | description: 'Simulating Failing HTTP Requests',
568 | 'duration': '7:54',
569 | 'seqNo': 7,
570 | courseId: 12
571 | },
572 |
573 | 47: {
574 | id: 47,
575 | description: 'Introduction to Angular Component Testing',
576 | 'duration': '5:31',
577 | 'seqNo': 8,
578 | courseId: 12
579 | },
580 |
581 | 48: {
582 | id: 48,
583 | description: 'Testing Angular Components without the DOM',
584 | 'duration': '8:19',
585 | 'seqNo': 9,
586 | courseId: 12
587 | },
588 |
589 | 49: {
590 | id: 49,
591 | description: 'Testing Angular Components with the DOM',
592 | 'duration': '7:05',
593 | 'seqNo': 10,
594 | courseId: 12
595 | },
596 |
597 |
598 | // Ngrx Course
599 | 50: {
600 | id: 50,
601 | 'description': 'Welcome to the Angular Ngrx Course',
602 | 'duration': '6:53',
603 | 'seqNo': 1,
604 | courseId: 4
605 |
606 | },
607 | 51: {
608 | id: 51,
609 | 'description': 'The Angular Ngrx Architecture Course - Helicopter View',
610 | 'duration': '5:52',
611 | 'seqNo': 2,
612 | courseId: 4
613 | },
614 | 52: {
615 | id: 52,
616 | 'description': 'The Origins of Flux - Understanding the Famous Facebook Bug Problem',
617 | 'duration': '8:17',
618 | 'seqNo': 3,
619 | courseId: 4
620 | },
621 | 53: {
622 | id: 53,
623 | 'description': 'Custom Global Events - Why Don\'t They Scale In Complexity?',
624 | 'duration': '7:47',
625 | 'seqNo': 4,
626 | courseId: 4
627 | },
628 | 54: {
629 | id: 54,
630 | 'description': 'The Flux Architecture - How Does it Solve Facebook Counter Problem?',
631 | 'duration': '9:22',
632 | 'seqNo': 5,
633 | courseId: 4
634 | },
635 | 55: {
636 | id: 55,
637 | 'description': 'Unidirectional Data Flow And The Angular Development Mode',
638 | 'duration': '7:07',
639 | 'seqNo': 6,
640 | courseId: 4
641 | },
642 |
643 | 56: {
644 | id: 56,
645 | 'description': 'Dispatching an Action - Implementing the Login Component',
646 | 'duration': '4:39',
647 | 'seqNo': 7,
648 | courseId: 4
649 | },
650 | 57: {
651 | id: 57,
652 | 'description': 'Setting Up the Ngrx DevTools - Demo',
653 | 'duration': '4:44',
654 | 'seqNo': 8,
655 | courseId: 4
656 | },
657 | 58: {
658 | id: 58,
659 | 'description': 'Understanding Reducers - Writing Our First Reducer',
660 | 'duration': '9:10',
661 | 'seqNo': 9,
662 | courseId: 4
663 | },
664 | 59: {
665 | id: 59,
666 | 'description': 'How To Define the Store Initial State',
667 | 'duration': '9:10',
668 | 'seqNo': 10,
669 | courseId: 4
670 | },
671 |
672 | // NestJs Course
673 |
674 | 60: {
675 | id: 60,
676 | 'description': 'Introduction to NestJs',
677 | 'duration': '4:29',
678 | 'seqNo': 1,
679 | courseId: 14
680 | },
681 | 61: {
682 | id: 61,
683 | 'description': 'Development Environment Setup',
684 | 'duration': '6:37',
685 | 'seqNo': 2,
686 | courseId: 14
687 | },
688 | 62: {
689 | id: 62,
690 | 'description': 'Setting up a MongoDB Database',
691 | 'duration': '6:38',
692 | 'seqNo': 3,
693 | courseId: 14
694 | },
695 | 63: {
696 | id: 63,
697 | 'description': 'CRUD with NestJs - Controllers and Repositories',
698 | 'duration': '12:12',
699 | 'seqNo': 4,
700 | courseId: 14
701 | },
702 | 64: {
703 | id: 64,
704 | 'description': 'First REST endpoint - Get All Courses',
705 | 'duration': '3:42',
706 | 'seqNo': 5,
707 | courseId: 14
708 | },
709 | 65: {
710 | id: 65,
711 | 'description': 'Error Handling',
712 | 'duration': '5:15',
713 | 'seqNo': 6,
714 | courseId: 14
715 | },
716 | 66: {
717 | id: 66,
718 | 'description': 'NestJs Middleware',
719 | 'duration': '7:08',
720 | 'seqNo': 7,
721 | courseId: 14
722 | },
723 | 67: {
724 | id: 67,
725 | 'description': 'Authentication in NestJs',
726 | 'duration': '13:22',
727 | 'seqNo': 8,
728 | courseId: 14
729 | },
730 | 68: {
731 | id: 68,
732 | 'description': 'Authorization in NestJs',
733 | 'duration': '6:43',
734 | 'seqNo': 9,
735 | courseId: 14
736 | },
737 | 69: {
738 | id: 69,
739 | 'description': 'Guards & Interceptors',
740 | 'duration': '8:16',
741 | 'seqNo': 10,
742 | courseId: 14
743 | },
744 |
745 | // Stripe Course
746 |
747 | 70: {
748 | id: 70,
749 | 'description': 'Introduction to Stripe Payments',
750 | 'duration': '03:45',
751 | 'seqNo': 0,
752 | courseId: 16
753 | },
754 | 71: {
755 | id: 71,
756 | 'description': 'The advantages of Stripe Checkout',
757 | 'duration': '08:36',
758 | 'seqNo': 1,
759 | courseId: 16
760 | },
761 | 72: {
762 | id: 72,
763 | 'description': 'Setting up the development environment',
764 | 'duration': '09:10',
765 | 'seqNo': 2,
766 | courseId: 16
767 | },
768 | 73: {
769 | id: 73,
770 | 'description': 'Creating a server Checkout Session',
771 | 'duration': '07:20',
772 | 'seqNo': 3,
773 | courseId: 16
774 | },
775 | 74: {
776 | id: 74,
777 | 'description': 'Redirecting to the Stripe Checkout page',
778 | 'duration': '11:47',
779 | 'seqNo': 4,
780 | courseId: 16
781 | },
782 | 75: {
783 | id: 75,
784 | 'description': 'Order fulfillment webhook',
785 | 'duration': '06:30',
786 | 'seqNo': 5,
787 | courseId: 16
788 | },
789 | 76: {
790 | id: 76,
791 | 'description': 'Installing the Stripe CLI',
792 | 'duration': '4:13',
793 | 'seqNo': 6,
794 | courseId: 16
795 | },
796 | 77: {
797 | id: 77,
798 | 'description': 'Firestore Security Rules for protecting Premium content',
799 | 'duration': '05:47',
800 | 'seqNo': 7,
801 | courseId: 16
802 | },
803 | 78: {
804 | id: 78,
805 | 'description': 'Stripe Subscriptions with Stripe Checkout',
806 | 'duration': '05:17',
807 | 'seqNo': 8,
808 | courseId: 16
809 | },
810 | 79: {
811 | id: 79,
812 | 'description': 'Stripe Subscription Fulfillment',
813 | 'duration': '07:50',
814 | 'seqNo': 9,
815 | courseId: 16
816 | },
817 |
818 |
819 | // Reactive Angular Course
820 |
821 | 80: {
822 | id: 80,
823 | 'description': 'Introduction to Reactive Programming',
824 | 'duration': '03:45',
825 | 'seqNo': 0,
826 | courseId: 17,
827 | videoId: 'Df1QnesgB_s',
828 | },
829 | 81: {
830 | id: 81,
831 | 'description': 'Introduction to RxJs',
832 | 'duration': '08:36',
833 | 'seqNo': 1,
834 | courseId: 17,
835 | videoId: '8m5RrAtqlyw',
836 | },
837 | 82: {
838 | id: 82,
839 | 'description': 'Setting up the development environment',
840 | 'duration': '09:10',
841 | 'seqNo': 2,
842 | courseId: 17,
843 | videoId: '3fDbUB-nKqc',
844 | },
845 | 83: {
846 | id: 83,
847 | 'description': 'Designing and building a Service Layer',
848 | 'duration': '07:20',
849 | 'seqNo': 3,
850 | courseId: 17,
851 | videoId: '',
852 | },
853 | 84: {
854 | id: 84,
855 | 'description': 'Stateless Observable Services',
856 | 'duration': '11:47',
857 | 'seqNo': 4,
858 | courseId: 17,
859 | videoId: 'qvDPnRs_ZPA',
860 | },
861 | 85: {
862 | id: 85,
863 | 'description': 'Smart vs Presentational Components',
864 | 'duration': '06:30',
865 | 'seqNo': 5,
866 | courseId: 17,
867 | videoId: '5bsZJGAelFM',
868 | },
869 | 86: {
870 | id: 86,
871 | 'description': 'Lightweight state management',
872 | 'duration': '4:13',
873 | 'seqNo': 6,
874 | courseId: 17,
875 | videoId: '9m3_HHeP9Ko',
876 | },
877 | 87: {
878 | id: 87,
879 | 'description': 'Event bubbling anti-pattern',
880 | 'duration': '05:47',
881 | 'seqNo': 7,
882 | courseId: 17,
883 | videoId: 'PRQCAL_RMVo',
884 | },
885 | 88: {
886 | id: 88,
887 | 'description': 'Master detail with cached master table',
888 | 'duration': '05:17',
889 | 'seqNo': 8,
890 | courseId: 17,
891 | videoId: 'du4ib4jBUG0'
892 | },
893 | 89: {
894 | id: 89,
895 | 'description': 'Error handling',
896 | 'duration': '07:50',
897 | 'seqNo': 9,
898 | courseId: 17,
899 | videoId: '8m5RrAtqlyw'
900 | },
901 |
902 |
903 |
904 | // Angular Router Course
905 | 90: {
906 | id: 90,
907 | 'description': 'What is a Single Page Application?',
908 | 'duration': '04:00',
909 | 'seqNo': 1,
910 | courseId: 18,
911 | videoId: 'VES1eTNxi1s'
912 | },
913 | 91: {
914 | id: 91,
915 | 'description': 'Setting Up The Development Environment',
916 | 'duration': '06:05',
917 | 'seqNo': 2,
918 | courseId: 18,
919 | videoId: 'ANfplcxnl78'
920 | },
921 | 92: {
922 | id: 92,
923 | 'description': 'Angular Router Setup',
924 | 'duration': '02:36',
925 | 'seqNo': 3,
926 | courseId: 18,
927 | videoId: '9ez72LAd6mM'
928 | },
929 | 93: {
930 | id: 93,
931 | 'description': 'Configuring a Home Route and Fallback Route',
932 | 'duration': '02:55',
933 | 'seqNo': 4,
934 | courseId: 18,
935 | videoId: 'Clj-jZpl64w'
936 | },
937 | 94: {
938 | id: 94,
939 | 'description': 'Styling Active Routes With The routerLinkActive And routerLinkActiveOptions',
940 | 'duration': '07:50',
941 | 'seqNo': 5,
942 | courseId: 18,
943 | videoId: 'zcgnsmPVc30'
944 | },
945 | 95: {
946 | id: 95,
947 | 'description': 'Child Routes - How To Setup a Master Detail Route',
948 | 'duration': '04:10',
949 | 'seqNo': 6,
950 | courseId: 18,
951 | videoId: 'zcgnsmPVc30'
952 | },
953 | 96: {
954 | id: 96,
955 | 'description': 'Programmatic Router Navigation via the Router API ',
956 | 'duration': '03:59',
957 | 'seqNo': 7,
958 | courseId: 18,
959 | videoId: 'VES1eTNxi1s'
960 | },
961 | 97: {
962 | id: 97,
963 | 'description': 'Relative And Absolute Router Navigation',
964 | 'duration': '04:58',
965 | 'seqNo': 8,
966 | courseId: 18,
967 | videoId: 'MQl9Zs3QqGM'
968 | },
969 | 98: {
970 | id: 98,
971 | 'description': 'Master Detail Navigation And Route Parameters',
972 | 'duration': '06:03',
973 | 'seqNo': 9,
974 | courseId: 18,
975 | videoId: 'ANfplcxnl78'
976 | },
977 |
978 | 99: {
979 | id: 99,
980 | 'description': 'The Route Parameters Observable',
981 | 'duration': '06:50',
982 | 'seqNo': 10,
983 | courseId: 18,
984 | videoId: 'zcgnsmPVc30'
985 | },
986 | 100: {
987 | id: 100,
988 | 'description': 'Optional Route Query Parameters',
989 | 'duration': '03:03',
990 | 'seqNo': 11,
991 | courseId: 18,
992 | videoId: '0Qsg8fyKwO4'
993 | },
994 | 101: {
995 | id: 101,
996 | 'description': 'The queryParams Directive and the Query Parameters Observable',
997 | 'duration': '07:50',
998 | 'seqNo': 12,
999 | courseId: 18,
1000 | videoId: 'VES1eTNxi1s'
1001 | },
1002 | 102: {
1003 | id: 102,
1004 | 'description': 'Exiting an Angular Route - How To Prevent Memory Leaks',
1005 | 'duration': '07:50',
1006 | 'seqNo': 13,
1007 | courseId: 18,
1008 | videoId: 'ANfplcxnl78'
1009 | },
1010 | 103: {
1011 | id: 103,
1012 | 'description': 'CanDeactivate Route Guard',
1013 | 'duration': '04:50',
1014 | 'seqNo': 14,
1015 | courseId: 18,
1016 | videoId: '9ez72LAd6mM'
1017 | },
1018 | 104: {
1019 | id: 104,
1020 | 'description': 'CanActivate Route Guard - An Example of An Asynchronous Route Guard',
1021 | 'duration': '03:32',
1022 | 'seqNo': 15,
1023 | courseId: 18,
1024 | videoId: 'Clj-jZpl64w'
1025 | },
1026 |
1027 |
1028 | 105: {
1029 | id: 105,
1030 | 'description': 'Configure Auxiliary Routes in the Angular Router',
1031 | 'duration': '05:16',
1032 | 'seqNo': 16,
1033 | courseId: 18,
1034 | videoId: 'zcgnsmPVc30'
1035 | },
1036 |
1037 | 106: {
1038 | id: 106,
1039 | 'description': 'Angular Auxiliary Routes - How To Pass Router Parameters',
1040 | 'duration': '07:50',
1041 | 'seqNo': 17,
1042 | courseId: 18,
1043 | videoId: 'yjQUkNHb1Is'
1044 | },
1045 | 107: {
1046 | id: 107,
1047 | 'description': 'Angular Router Redirects and Path Matching',
1048 | 'duration': '02:59',
1049 | 'seqNo': 18,
1050 | courseId: 18,
1051 | videoId: 'VES1eTNxi1s'
1052 | },
1053 | 108: {
1054 | id: 108,
1055 | 'description': 'Angular Router Hash Location Strategy',
1056 | 'duration': '07:50',
1057 | 'seqNo': 19,
1058 | courseId: 18,
1059 | videoId: 'MQl9Zs3QqGM'
1060 | },
1061 | 109: {
1062 | id: 109,
1063 | 'description': 'Angular Router Lazy Loading and Shared Modules',
1064 | 'duration': '08:45',
1065 | 'seqNo': 20,
1066 | courseId: 18,
1067 | videoId: '0Qsg8fyKwO4'
1068 | },
1069 | 110: {
1070 | id: 110,
1071 | 'description': 'Exercise - Implement a Widget Dashboard',
1072 | 'duration': '07:50',
1073 | 'seqNo': 21,
1074 | courseId: 18,
1075 | videoId: 'VES1eTNxi1s'
1076 | },
1077 | 111: {
1078 | id: 111,
1079 | 'description': 'Exercise Solution ',
1080 | 'duration': '07:50',
1081 | 'seqNo': 22,
1082 | courseId: 18,
1083 | videoId: '0Qsg8fyKwO4'
1084 | },
1085 |
1086 | // Angular Material In Depth
1087 |
1088 | 120: {
1089 | id: 120,
1090 | 'description': 'Introduction to Angular Material',
1091 | 'duration': '4:17',
1092 | 'seqNo': 1,
1093 | courseId: 11,
1094 | longDescription: "A quick introduction to the Angular Material library."
1095 | },
1096 | 121: {
1097 | id: 121,
1098 | 'description': 'Navigation and Containers',
1099 | 'duration': '6:37',
1100 | 'seqNo': 2,
1101 | courseId: 11,
1102 | longDescription: "Guided tour of navigation elements and container."
1103 | },
1104 | 122: {
1105 | id: 122,
1106 | 'description': 'Data Tables',
1107 | 'duration': '8:03',
1108 | 'seqNo': 3,
1109 | courseId: 11,
1110 | longDescription: "Angular Material Data Tables in detail."
1111 | },
1112 | 123: {
1113 | id: 123,
1114 | 'description': 'Dialogs',
1115 | 'duration': '11:46',
1116 | 'seqNo': 4,
1117 | courseId: 11,
1118 | longDescription: "Modal elements and how to use them."
1119 | },
1120 | 124: {
1121 | id: 124,
1122 | 'description': 'Commonly used Form Controls',
1123 | 'duration': '7:17',
1124 | 'seqNo': 5,
1125 | courseId: 11,
1126 | longDescription: "All sorts of commonly needed form controls."
1127 | },
1128 | 125: {
1129 | id: 125,
1130 | 'description': 'Drag and Drop',
1131 | 'duration': '8:16',
1132 | 'seqNo': 6,
1133 | courseId: 11,
1134 | longDescription: "How to use drag and drop."
1135 | },
1136 | 126: {
1137 | id: 126,
1138 | 'description': 'Responsive Design',
1139 | 'duration': '7:28',
1140 | 'seqNo': 7,
1141 | courseId: 11,
1142 | longDescription: "Everything about making our screens responsive."
1143 | },
1144 | 127: {
1145 | id: 127,
1146 | 'description': 'Tree Component',
1147 | 'duration': '11:09',
1148 | 'seqNo': 8,
1149 | courseId: 11,
1150 | longDescription: "All about the Angular Material Tree component."
1151 | },
1152 | 128: {
1153 | id: 128,
1154 | 'description': 'Virtual Scrolling',
1155 | 'duration': '3:44',
1156 | 'seqNo': 9,
1157 | courseId: 11,
1158 | longDescription: "How to use virtual scrolling to handle large amounts of data."
1159 | },
1160 | 129: {
1161 | id: 129,
1162 | 'description': 'Custom Themes',
1163 | 'duration': '8:55',
1164 | 'seqNo': 10,
1165 | courseId: 11,
1166 | longDescription: "How to build your own custom Angular Material theme."
1167 | },
1168 | 130: {
1169 | id: 130,
1170 | 'description': 'Changing Theme at Runtime',
1171 | 'duration': '12:37',
1172 | 'seqNo': 11,
1173 | courseId: 11,
1174 | longDescription: ""
1175 | }
1176 | };
1177 |
1178 |
1179 | export const USERS = {
1180 | 1: {
1181 | id: 1,
1182 | email: 'test@angular-university.io',
1183 | password: 'test',
1184 | pictureUrl: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png'
1185 | }
1186 |
1187 | };
1188 |
1189 |
1190 | export function findCourseById(courseId: number) {
1191 | return COURSES[courseId];
1192 | }
1193 |
1194 | export function findLessonsForCourse(courseId: number) {
1195 | return Object.values(LESSONS).filter(lesson => lesson.courseId == courseId);
1196 | }
1197 |
1198 | export function authenticate(email: string, password: string) {
1199 |
1200 | const user: any = Object.values(USERS).find(user => user.email === email);
1201 |
1202 | if (user && user.password == password) {
1203 | return user;
1204 | } else {
1205 | return undefined;
1206 | }
1207 |
1208 | }
1209 |
--------------------------------------------------------------------------------