├── .node-version ├── public ├── favicon.ico ├── images │ ├── coin.png │ ├── de.webp │ ├── en.webp │ ├── euro.png │ ├── logo.png │ ├── user1.jpg │ ├── user2.jpg │ ├── user3.jpg │ ├── crown.webp │ ├── crown1.png │ ├── crown2.png │ ├── crown3.webp │ ├── about-1.webp │ ├── about-2.webp │ ├── about-3.webp │ ├── about-4.webp │ ├── about-5.webp │ ├── logo-text.png │ ├── certificate.png │ ├── icons │ │ ├── icon-144.png │ │ ├── icon-168.png │ │ ├── icon-196.png │ │ ├── icon-48.png │ │ ├── icon-72.png │ │ └── icon-96.png │ ├── screenshots │ │ ├── why.png │ │ ├── features.png │ │ ├── startpage.png │ │ ├── why_mobile.png │ │ └── startpage_mobile.png │ └── sponsors │ │ ├── BWB.webp │ │ ├── FHDW.webp │ │ ├── GBI.webp │ │ ├── ITCS.webp │ │ ├── MT5.webp │ │ └── WINGO.webp ├── fonts │ ├── Space_Mono │ │ ├── SpaceMono-Bold.ttf │ │ ├── SpaceMono-Italic.ttf │ │ ├── SpaceMono-Regular.ttf │ │ └── SpaceMono-BoldItalic.ttf │ └── Ubuntu_Mono │ │ ├── UbuntuMono-Bold.ttf │ │ ├── UbuntuMono-Italic.ttf │ │ ├── UbuntuMono-Regular.ttf │ │ └── UbuntuMono-BoldItalic.ttf └── svgs │ ├── kotlin_grundlagen.svg │ ├── kotlin_fuer_fortgeschrittene.svg │ ├── nginx.svg │ ├── angular.svg │ ├── windows_grundlagen.svg │ ├── mathematik_fuer_informatiker.svg │ ├── pytorch.svg │ ├── c_grundlagen.svg │ ├── grundlagen_der_netzwerktechnik.svg │ ├── powershell_und_fortgeschrittene_windows_administration.svg │ ├── wireshark.svg │ ├── rechnernetze.svg │ ├── kotlin_native_app_developer.svg │ ├── html_und_css.svg │ ├── dart_und_flutter_experte.svg │ ├── c_plus_plus.svg │ ├── c_sharp_grundlagen.svg │ ├── c_sharp_fuer_fortgeschrittene.svg │ ├── grundlagen_der_it_sicherheit.svg │ ├── php_grundlagen.svg │ ├── it_forensiker.svg │ ├── grundlagen_der_programmierung_und_datenhaltung.svg │ ├── uml.svg │ ├── datenschutz_und_privacy.svg │ ├── tensorflow_mit_python.svg │ ├── vernetzte_systeme.svg │ └── typescript.svg ├── .gitignore ├── layouts ├── errorLayout.vue ├── empty.vue ├── inner.vue └── default.vue ├── .prettierignore ├── .vscode └── settings.json ├── types ├── form.d.ts ├── skillTreeTypes.ts ├── userTypes.ts ├── matching.ts └── calenderTypes.ts ├── .envrc ├── tsconfig.json ├── middleware ├── auth.ts ├── redirect-authenticated.ts └── route.global.ts ├── .prettierrc ├── composables ├── contact.ts ├── dialogSlot.ts ├── rating.ts ├── xp.ts ├── hearts.ts ├── bookmark.ts ├── mfa.ts ├── coaching.ts ├── premiumFeature.ts ├── calendar.ts ├── coins.ts ├── response.ts └── newsletter.ts ├── components ├── skill-tree │ ├── NodeDetailsCard.vue │ ├── ProgressEmptyState.vue │ ├── Pathway.vue │ └── ZoomLevel.vue ├── matching │ └── List.vue ├── quiz │ ├── List.vue │ └── CardSkeleton.vue ├── svg │ ├── Level9Icon.vue │ ├── Star.vue │ ├── StarOutline.vue │ ├── FullHeart.vue │ ├── HalfHeart.vue │ ├── Level8Icon.vue │ ├── Level6Icon.vue │ ├── Level1Icon.vue │ ├── Heart.vue │ ├── Level2Icon.vue │ ├── Level7Icon.vue │ ├── Level3Icon.vue │ ├── Level5Icon.vue │ └── Level4Icon.vue ├── course │ ├── CardEmptyState.vue │ ├── EmptyState.vue │ ├── CardSmEmptyState.vue │ ├── CardSmSkeleton.vue │ ├── CardSkeleton.vue │ ├── CardSm.vue │ └── Details.vue ├── job │ ├── EmptyState.vue │ ├── CardEmptyState.vue │ └── CardSkeleton.vue ├── leaderboard │ ├── Seasonal.vue │ ├── Overall.vue │ ├── ChallengeBasedListDialog.vue │ └── TopUserCard.vue ├── icon │ ├── Check.vue │ ├── Twitter.vue │ ├── ListBullet.vue │ ├── index.vue │ ├── Bug.vue │ ├── Coaching.vue │ ├── Youtube.vue │ ├── Skill.vue │ ├── Discord.vue │ ├── Loading.vue │ ├── Google.vue │ ├── Github.vue │ └── Instagram.vue ├── skeleton │ ├── QuizAnswer.vue │ ├── Media.vue │ ├── LeaderBoardChallenges.vue │ ├── List.vue │ ├── SolveMatching.vue │ ├── CodingChallengeList.vue │ ├── FormCodingChallenge.vue │ └── Text.vue ├── challenges │ ├── ItemDescription.vue │ ├── ItemDuration.vue │ ├── MyRank.vue │ ├── ProgressSummary.vue │ └── ItemLimits.vue ├── user │ ├── XP.vue │ ├── RefillHeartBtn.vue │ ├── Coins.vue │ └── Progress.vue ├── hero │ ├── Sponsors.vue │ ├── FAQS.vue │ └── Landing.vue ├── LoadingDots.vue ├── Accordion.vue ├── Download.vue ├── Modal.vue ├── Loading.vue ├── input │ ├── Switch.vue │ └── RadioGroup.vue ├── MarkdownEditor.vue ├── subscription │ ├── Timer.vue │ └── PremiumUntillCountDown.vue ├── Sort.vue ├── Language.vue ├── List.vue ├── SubMenu.vue ├── Navbar.vue └── account │ ├── VerifyEmail.vue │ └── MFA.vue ├── plugins ├── recaptcha.ts └── i18n.ts ├── pages ├── hearts │ └── buy.vue ├── morphcoins │ ├── buy.vue │ └── error.vue ├── skill-tree │ └── progress │ │ └── [id].vue ├── challenges │ ├── [category] │ │ └── create.vue │ ├── index.vue │ └── all.vue ├── dashboard │ └── index.vue ├── index.vue ├── quizzes │ └── edit-[id].vue ├── profile │ ├── index.vue │ └── edit.vue ├── auth │ ├── login.vue │ ├── reset-password.vue │ ├── forgot-password.vue │ └── signup.vue ├── account │ ├── index.vue │ └── mfa │ │ ├── disabled.vue │ │ └── disable.vue ├── webinars │ ├── create │ │ └── [skill].vue │ ├── [id].vue │ └── index.vue └── calendar.vue ├── renovate.json ├── .github ├── ISSUE_TEMPLATE │ └── config.yml ├── workflows │ ├── check.yml │ └── build.yml └── pull_request_template.md ├── eslint.config.js ├── README.md ├── package.json └── description.ts /.node-version: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/images/coin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/coin.png -------------------------------------------------------------------------------- /public/images/de.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/de.webp -------------------------------------------------------------------------------- /public/images/en.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/en.webp -------------------------------------------------------------------------------- /public/images/euro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/euro.png -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/logo.png -------------------------------------------------------------------------------- /public/images/user1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/user1.jpg -------------------------------------------------------------------------------- /public/images/user2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/user2.jpg -------------------------------------------------------------------------------- /public/images/user3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/user3.jpg -------------------------------------------------------------------------------- /public/images/crown.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/crown.webp -------------------------------------------------------------------------------- /public/images/crown1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/crown1.png -------------------------------------------------------------------------------- /public/images/crown2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/crown2.png -------------------------------------------------------------------------------- /public/images/crown3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/crown3.webp -------------------------------------------------------------------------------- /public/images/about-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/about-1.webp -------------------------------------------------------------------------------- /public/images/about-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/about-2.webp -------------------------------------------------------------------------------- /public/images/about-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/about-3.webp -------------------------------------------------------------------------------- /public/images/about-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/about-4.webp -------------------------------------------------------------------------------- /public/images/about-5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/about-5.webp -------------------------------------------------------------------------------- /public/images/logo-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/logo-text.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | .nuxt 4 | .nitro 5 | .cache 6 | .output 7 | .env 8 | dist 9 | /.direnv 10 | -------------------------------------------------------------------------------- /public/images/certificate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/certificate.png -------------------------------------------------------------------------------- /public/images/icons/icon-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/icons/icon-144.png -------------------------------------------------------------------------------- /public/images/icons/icon-168.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/icons/icon-168.png -------------------------------------------------------------------------------- /public/images/icons/icon-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/icons/icon-196.png -------------------------------------------------------------------------------- /public/images/icons/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/icons/icon-48.png -------------------------------------------------------------------------------- /public/images/icons/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/icons/icon-72.png -------------------------------------------------------------------------------- /public/images/icons/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/icons/icon-96.png -------------------------------------------------------------------------------- /public/images/screenshots/why.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/screenshots/why.png -------------------------------------------------------------------------------- /public/images/sponsors/BWB.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/sponsors/BWB.webp -------------------------------------------------------------------------------- /public/images/sponsors/FHDW.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/sponsors/FHDW.webp -------------------------------------------------------------------------------- /public/images/sponsors/GBI.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/sponsors/GBI.webp -------------------------------------------------------------------------------- /public/images/sponsors/ITCS.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/sponsors/ITCS.webp -------------------------------------------------------------------------------- /public/images/sponsors/MT5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/sponsors/MT5.webp -------------------------------------------------------------------------------- /public/images/sponsors/WINGO.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/sponsors/WINGO.webp -------------------------------------------------------------------------------- /public/images/screenshots/features.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/screenshots/features.png -------------------------------------------------------------------------------- /public/images/screenshots/startpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/screenshots/startpage.png -------------------------------------------------------------------------------- /public/images/screenshots/why_mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/screenshots/why_mobile.png -------------------------------------------------------------------------------- /layouts/errorLayout.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/fonts/Space_Mono/SpaceMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/fonts/Space_Mono/SpaceMono-Bold.ttf -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .nuxt 2 | .output 3 | .vscode 4 | .idea 5 | dist 6 | node_modules 7 | package-lock.json 8 | public 9 | coverage 10 | *.log 11 | -------------------------------------------------------------------------------- /public/fonts/Space_Mono/SpaceMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/fonts/Space_Mono/SpaceMono-Italic.ttf -------------------------------------------------------------------------------- /public/fonts/Space_Mono/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/fonts/Space_Mono/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/Ubuntu_Mono/UbuntuMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/fonts/Ubuntu_Mono/UbuntuMono-Bold.ttf -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["coachings", "Morphcoin", "morphcoins", "Recaptcha", "TOTP"], 3 | "vue.codeActions.enabled": false 4 | } 5 | -------------------------------------------------------------------------------- /public/fonts/Space_Mono/SpaceMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/fonts/Space_Mono/SpaceMono-BoldItalic.ttf -------------------------------------------------------------------------------- /public/fonts/Ubuntu_Mono/UbuntuMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/fonts/Ubuntu_Mono/UbuntuMono-Italic.ttf -------------------------------------------------------------------------------- /public/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf -------------------------------------------------------------------------------- /public/images/screenshots/startpage_mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/images/screenshots/startpage_mobile.png -------------------------------------------------------------------------------- /public/fonts/Ubuntu_Mono/UbuntuMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bootstrap-Academy/frontend/HEAD/public/fonts/Ubuntu_Mono/UbuntuMono-BoldItalic.ttf -------------------------------------------------------------------------------- /types/form.d.ts: -------------------------------------------------------------------------------- 1 | export interface IForm { 2 | [inputs: string]: any; 3 | submitting: boolean; 4 | validate: () => boolean; 5 | body: () => any; 6 | } 7 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | on_git_branch latest && exit 2 | if has nix; then 3 | use nix -p nodejs_18 nodePackages.volar nodePackages.typescript-language-server 4 | fi 5 | layout node 6 | -------------------------------------------------------------------------------- /layouts/empty.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://v3.nuxtjs.org/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json", 4 | "buildOptions": { 5 | "volar.inlayHints.eventArgumentInInlineHandlers": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /middleware/auth.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtRouteMiddleware((to, from) => { 2 | const cookie_accessToken = useCookie("accessToken"); 3 | if (!!!cookie_accessToken.value) { 4 | return navigateTo("/auth/login"); 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /layouts/inner.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /middleware/redirect-authenticated.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtRouteMiddleware((to) => { 2 | const accessToken = useCookie("accessToken"); 3 | 4 | if (!!accessToken.value && to.path === "/") { 5 | return navigateTo("/dashboard"); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "plugins": ["prettier-plugin-tailwindcss"], 4 | "printWidth": 100, 5 | "semi": true, 6 | "singleQuote": false, 7 | "trailingComma": "es5", 8 | "tabWidth": 2 9 | } 10 | -------------------------------------------------------------------------------- /composables/contact.ts: -------------------------------------------------------------------------------- 1 | export async function contact(body: any) { 2 | try { 3 | const response = await POST(`/auth/contact`, body); 4 | 5 | return [response, null]; 6 | } catch (error: any) { 7 | return [null, error.data]; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /components/skill-tree/NodeDetailsCard.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /components/matching/List.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /plugins/recaptcha.ts: -------------------------------------------------------------------------------- 1 | import { VueReCaptcha } from "vue-recaptcha-v3"; 2 | 3 | export default defineNuxtPlugin(({ vueApp }) => { 4 | const config = useRuntimeConfig().public; 5 | 6 | vueApp.use(VueReCaptcha, { 7 | siteKey: config.Vue3ReCaptcha_SITE_KEY, 8 | loaderOptions: { 9 | useRecaptchaNet: true, 10 | autoHideBadge: true, 11 | }, 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /components/quiz/List.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /components/svg/Level9Icon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /components/course/CardEmptyState.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /components/job/EmptyState.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /components/leaderboard/Seasonal.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /components/course/EmptyState.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /components/icon/Check.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /pages/hearts/buy.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /pages/morphcoins/buy.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | 4 | "extends": ["config:recommended"], 5 | 6 | "lockFileMaintenance": { 7 | "enabled": true, 8 | "automerge": true, 9 | "schedule": ["* 0 1,15 * *"] 10 | }, 11 | 12 | "packageRules": [ 13 | { 14 | "matchUpdateTypes": ["minor", "patch"], 15 | "matchManagers": ["npm"], 16 | "automerge": true 17 | } 18 | ], 19 | 20 | "nodenv": { 21 | "enabled": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /components/skill-tree/ProgressEmptyState.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Open a new ticket 4 | url: "https://github.com/Bootstrap-Academy/Bootstrap-Academy/issues/new/choose" 5 | about: "Tickets are managed in the Bootstrap-Academy repository." 6 | - name: Discord 7 | url: "https://the-morpheus.de/discord.html" 8 | about: "Join the Discord server of The Morpheus Tutorials." 9 | - name: Bootstrap Academy 10 | url: "https://bootstrap.academy/contact" 11 | about: "Go to https://bootstrap.academy/contact for information on how to contact us." 12 | -------------------------------------------------------------------------------- /public/svgs/kotlin_grundlagen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/svgs/kotlin_fuer_fortgeschrittene.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /components/job/CardEmptyState.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/svgs/nginx.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /components/svg/Star.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /public/svgs/angular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: check 2 | 3 | on: 4 | push: 5 | branches: [develop] 6 | pull_request: 7 | merge_group: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | lint: 14 | name: lint 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | - name: Setup Node 20 | uses: actions/setup-node@v6 21 | with: 22 | node-version: 24.12.0 23 | - name: Install dependencies 24 | run: npm ci 25 | - name: Check formatting 26 | run: npm run format:check 27 | - name: Lint 28 | run: npm run lint 29 | -------------------------------------------------------------------------------- /components/quiz/CardSkeleton.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /plugins/i18n.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from "vue-i18n"; 2 | import en from "../locales/en-US.json"; 3 | import de from "../locales/de.json"; 4 | 5 | export default defineNuxtPlugin(({ vueApp }) => { 6 | const locale = useCookie("locale"); 7 | const i18n = createI18n({ 8 | legacy: false, 9 | globalInjection: true, 10 | messages: { "en-US": en, de }, 11 | defaultLocale: "de", 12 | locale: locale.value || "de", 13 | loadLanguagesAsync: true, 14 | langDir: "~/locales", 15 | strategy: "no_prefix", 16 | missingWarn: false, 17 | fallbackWarn: false, 18 | silentTranslationWarn: true, 19 | }); 20 | 21 | vueApp.use(i18n); 22 | }); 23 | -------------------------------------------------------------------------------- /components/course/CardSmEmptyState.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /components/icon/Twitter.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /components/course/CardSmSkeleton.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /pages/skill-tree/progress/[id].vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 21 | 22 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /components/icon/ListBullet.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /composables/dialogSlot.ts: -------------------------------------------------------------------------------- 1 | export const useDialogSlot = () => useState("dialogSlot", () => false); 2 | export const useDialogCreateCodingChallenge = () => 3 | useState("dialogCreateCodingChallenge", () => false); 4 | export const useDialogCreateSubtask = () => useState("dialogCreateSubtask", () => false); 5 | export const useDialogReportTask = () => useState("dialogReportTask", () => false); 6 | export const useDialogCodingChallengeFeedback = () => 7 | useState("dalogCodingChallengeFeedback", () => false); 8 | export const useDialogLeaderBoardChallengeBased = () => 9 | useState("dialogLeaderBoardChallengeBased", () => false); 10 | export const useDialogCreateMatching = () => useState("dialogCreateMatching", () => false); 11 | -------------------------------------------------------------------------------- /components/icon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /types/skillTreeTypes.ts: -------------------------------------------------------------------------------- 1 | export class SkillTree { 2 | rows: number = 0; 3 | columns: number = 0; 4 | skills: T[] = []; 5 | } 6 | 7 | export class RootSkill { 8 | row: number = 0; 9 | column: number = 0; 10 | id: string = ""; 11 | name: string = ""; 12 | dependencies: string[] = []; 13 | dependents: string[] = []; 14 | skills: string[] = []; 15 | sub_tree_rows: number = 0; 16 | sub_tree_columns: number = 0; 17 | icon: string = ""; 18 | } 19 | 20 | export class SubSkill { 21 | id: string = ""; 22 | parent_id: string = ""; 23 | name: string = ""; 24 | icon: string = ""; 25 | row: number = 0; 26 | column: number = 0; 27 | courses: string[] = []; 28 | dependencies: string[] = []; 29 | dependents: string[] = []; 30 | } 31 | -------------------------------------------------------------------------------- /components/svg/StarOutline.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /components/skeleton/QuizAnswer.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /pages/challenges/[category]/create.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | 5 | ## Type of change 6 | 7 | - [ ] Bug fix (non-breaking change which fixes an issue) 8 | - [ ] New feature (non-breaking change which adds functionality) 9 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 10 | - [ ] Translation updates (fix/improve or add new translations) 11 | 12 | 13 | 14 | Fixes Bootstrap-Academy/Bootstrap-Academy#ISSUE-NUMBER 15 | 16 | 17 | 18 | My Bootstrap Academy username: 19 | -------------------------------------------------------------------------------- /components/skeleton/Media.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/svgs/windows_grundlagen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /components/skeleton/LeaderBoardChallenges.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /types/userTypes.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | admin: boolean = false; 3 | avatar_url: string = ""; 4 | business: boolean = false; 5 | can_buy_coins: boolean = false; 6 | can_receive_coins: boolean = false; 7 | city: string = ""; 8 | country: string = ""; 9 | description: string = ""; 10 | display_name: string = ""; 11 | email: string = ""; 12 | email_verified: boolean = false; 13 | first_name: string = ""; 14 | id: string = ""; 15 | last_login: number = 0; 16 | last_name: string = ""; 17 | last_name_change: number = 0; 18 | mfa_enabled: boolean = false; 19 | name: string = ""; 20 | newsLetter: boolean = false; 21 | password: boolean = false; 22 | registration: number = 0; 23 | street: string = ""; 24 | tags: unknown[] = []; 25 | vat_id: null | unknown = null; 26 | zip_code: string = ""; 27 | } 28 | -------------------------------------------------------------------------------- /public/svgs/mathematik_fuer_informatiker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /types/matching.ts: -------------------------------------------------------------------------------- 1 | export type matchingOptionArray = Array; 2 | 3 | export type matchingSolutionArray = Array; 4 | 5 | export type subTasksType = "MATCHING" | "CODING_CHALLENGE" | "MCQ"; 6 | 7 | export class Matching { 8 | left: matchingOptionArray = []; 9 | right: matchingOptionArray = []; 10 | solution: matchingSolutionArray = []; 11 | creator: string = ""; 12 | coins: number = 0; 13 | enabled: boolean = false; 14 | rated: boolean = false; 15 | retired: boolean = false; 16 | solved: boolean = false; 17 | task_id: string = ""; 18 | type: subTasksType = "MATCHING"; 19 | xp: number = 0; 20 | id: string = ""; 21 | } 22 | 23 | export class MatchingForSections { 24 | constructor( 25 | public sectionId: string = "", 26 | public lectureId: string = "", 27 | public matching: Matching = new Matching() 28 | ) {} 29 | } 30 | -------------------------------------------------------------------------------- /components/challenges/ItemDescription.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /components/user/XP.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /components/svg/FullHeart.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /middleware/route.global.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtRouteMiddleware((to, from) => { 2 | if (to.path !== from.path && process.client) { 3 | setTimeout(() => { 4 | window.scrollTo({ 5 | top: 0, 6 | left: 0, 7 | behavior: "smooth", 8 | }); 9 | }, 450); 10 | } 11 | 12 | if ( 13 | (!to.path.includes("/docs/privacy") && to.path.includes("/privacy")) || 14 | (to.path.includes("/datenschutz") && !to.path.includes("/skill-tree")) 15 | ) { 16 | return navigateTo("/docs/privacy"); 17 | } 18 | 19 | if (to.path.includes("impressum")) { 20 | return navigateTo("/docs/imprint"); 21 | } 22 | 23 | const cookie_accessToken = useCookie("accessToken"); 24 | 25 | if (to.path.includes("/auth/login") && !!cookie_accessToken.value) { 26 | console.log("he", cookie_accessToken.value); 27 | return navigateTo("/profile"); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /components/icon/Bug.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /components/hero/Sponsors.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /public/svgs/pytorch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /components/LoadingDots.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 47 | -------------------------------------------------------------------------------- /pages/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /components/Accordion.vue: -------------------------------------------------------------------------------- 1 | ! 2 | 17 | 18 | 29 | 30 | 42 | -------------------------------------------------------------------------------- /components/leaderboard/Overall.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /composables/rating.ts: -------------------------------------------------------------------------------- 1 | export const useUnratedWebinars = () => useState("unratedWebinars", () => []); 2 | 3 | export async function getUnratedWebinars() { 4 | try { 5 | const response = await GET(`/events/unrated`); 6 | 7 | const unratedWebinars = useUnratedWebinars(); 8 | unratedWebinars.value = response ?? []; 9 | 10 | return [response, null]; 11 | } catch (error: any) { 12 | return [null, error.data]; 13 | } 14 | } 15 | 16 | export async function submitWebinarRating(id: string, rating: number) { 17 | try { 18 | const response = await POST(`/events/rate/${id}`, { rating }); 19 | 20 | return [response, null]; 21 | } catch (error: any) { 22 | return [null, error.data]; 23 | } 24 | } 25 | 26 | export async function cancelWebinarRating(id: string) { 27 | try { 28 | const response = await DELETE(`/events/rate/${id}`); 29 | 30 | return [response, null]; 31 | } catch (error: any) { 32 | return [null, error.data]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /composables/xp.ts: -------------------------------------------------------------------------------- 1 | export const useXP = () => useState("xp", (): any => null); 2 | import { useUser } from "./user"; 3 | export async function getXP() { 4 | const user = useUser(); 5 | 6 | try { 7 | if (!!!user.value || !!!user.value.id) { 8 | throw { data: { detail: "Missing user id" } }; 9 | } 10 | 11 | const response = await GET(`/skills/xp/${user.value.id}`); 12 | 13 | const xp = useXP(); 14 | xp.value = response; 15 | return [response, null]; 16 | } catch (error: any) { 17 | return [null, error.data]; 18 | } 19 | } 20 | 21 | const skillsProgress = computed(() => { 22 | const xp = useXP(); 23 | return xp?.value?.skills ?? []; 24 | }); 25 | 26 | export const activeSkillsProgress = computed(() => { 27 | return skillsProgress.value.filter((skill: any) => skill.progress > 0); 28 | }); 29 | 30 | export const completedSkillsProgress = computed(() => { 31 | return skillsProgress.value.filter((skill: any) => skill.progress == 100); 32 | }); 33 | -------------------------------------------------------------------------------- /components/Download.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /components/Modal.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 26 | 27 | 44 | -------------------------------------------------------------------------------- /public/svgs/c_grundlagen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/svgs/grundlagen_der_netzwerktechnik.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/svgs/powershell_und_fortgeschrittene_windows_administration.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /components/svg/HalfHeart.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /components/icon/Coaching.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /components/svg/Level8Icon.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /components/icon/Youtube.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /components/icon/Skill.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /composables/hearts.ts: -------------------------------------------------------------------------------- 1 | export const useHeartInfo = () => useState("heartInfo", () => null); 2 | 3 | export async function refillHearts() { 4 | try { 5 | const res = await PUT(`/shop/hearts`); 6 | const heartInfo = useHeartInfo(); 7 | heartInfo.value = res?.hearts ?? 0; 8 | await getHearts(); 9 | await getBalance(); 10 | return [res, null]; 11 | } catch (error: any) { 12 | return [null, error]; 13 | } 14 | } 15 | export async function heartsConfig() { 16 | try { 17 | const res = await GET(`/shop/hearts/config`); 18 | const heartInfo = useHeartInfo(); 19 | heartInfo.value = res?.hearts ?? 0; 20 | return [res, null]; 21 | } catch (error: any) { 22 | return [null, error]; 23 | } 24 | } 25 | export async function getHearts() { 26 | try { 27 | const user: any = useUser(); 28 | const res = await GET(`/shop/hearts/${user.value.id}`); 29 | const heartInfo = useHeartInfo(); 30 | heartInfo.value = res ?? null; 31 | return [res, null]; 32 | } catch (error: any) { 33 | return [null, error]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pages/quizzes/edit-[id].vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/svgs/wireshark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/svgs/rechnernetze.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /components/svg/Level6Icon.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /components/svg/Level1Icon.vue: -------------------------------------------------------------------------------- 1 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /components/challenges/ItemDuration.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /components/svg/Heart.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 49 | -------------------------------------------------------------------------------- /composables/bookmark.ts: -------------------------------------------------------------------------------- 1 | export async function createBookmark( 2 | rootSkillId: string, 3 | subSkillId: string | null = null 4 | ): Promise { 5 | try { 6 | var urlPath = `/skills/bookmark/${rootSkillId}/${subSkillId != null ? subSkillId : ""}`; 7 | 8 | const response = await POST(urlPath); 9 | 10 | if (!response) throw new Error("Failed to create bookmark"); 11 | 12 | console.log("Bookmark created"); 13 | } catch (error) { 14 | console.error("Error creating bookmark", error); 15 | throw error; 16 | } 17 | } 18 | 19 | export async function deleteBookmark( 20 | rootSkillId: string, 21 | subSkillId: string | null = null 22 | ): Promise { 23 | try { 24 | var urlPath = `/skills/bookmark/${rootSkillId}/${subSkillId != null ? subSkillId : ""}`; 25 | 26 | const response = await DELETE(urlPath); 27 | 28 | if (!response || response !== true) throw new Error("Failed to delete bookmark"); 29 | 30 | console.log("Bookmark deleted"); 31 | } catch (error) { 32 | console.error("Error deleting bookmark", error); 33 | throw error; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [develop] 6 | pull_request: 7 | merge_group: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | env: 17 | NODE_OPTIONS: "--max_old_space_size=4096" 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v6 21 | - name: Setup Node 22 | uses: actions/setup-node@v6 23 | with: 24 | node-version: 24.12.0 25 | - name: Install dependencies 26 | run: npm ci 27 | - name: Build 28 | run: bash build.sh 29 | - name: Save PR number 30 | if: github.event_name == 'pull_request' 31 | run: | 32 | echo ${{ github.event.number }} > .pr 33 | - name: Upload dist 34 | if: github.event_name == 'pull_request' 35 | uses: actions/upload-artifact@v6 36 | with: 37 | name: dist 38 | path: | 39 | dist/ 40 | .pr 41 | retention-days: 1 42 | include-hidden-files: true 43 | -------------------------------------------------------------------------------- /components/svg/Level2Icon.vue: -------------------------------------------------------------------------------- 1 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /components/Loading.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | 24 | 49 | -------------------------------------------------------------------------------- /pages/profile/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 34 | 35 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /components/challenges/MyRank.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 38 | -------------------------------------------------------------------------------- /pages/auth/login.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 42 | -------------------------------------------------------------------------------- /components/input/Switch.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("eslint/config"); 2 | const vueParser = require("vue-eslint-parser"); 3 | const tsParser = require("@typescript-eslint/parser"); 4 | const prettierConfig = require("eslint-config-prettier/flat"); 5 | const vuePlugin = require("eslint-plugin-vue"); 6 | 7 | module.exports = defineConfig([ 8 | { 9 | ignores: ["**/node_modules/**", "**/.nuxt/**", "**/.output/**", "dist/**"], 10 | }, 11 | { 12 | files: ["**/*.vue"], 13 | plugins: { 14 | vue: vuePlugin, 15 | }, 16 | languageOptions: { 17 | parser: vueParser, 18 | parserOptions: { 19 | parser: require.resolve("@typescript-eslint/parser"), 20 | sourceType: "module", 21 | ecmaVersion: "latest", 22 | }, 23 | }, 24 | rules: {}, 25 | }, 26 | { 27 | files: ["**/*.{ts,tsx}"], 28 | languageOptions: { 29 | parser: tsParser, 30 | sourceType: "module", 31 | ecmaVersion: "latest", 32 | }, 33 | }, 34 | { 35 | files: ["**/*.{js,jsx}"], 36 | languageOptions: { 37 | ecmaVersion: "latest", 38 | sourceType: "module", 39 | }, 40 | }, 41 | prettierConfig, 42 | ]); 43 | -------------------------------------------------------------------------------- /public/svgs/kotlin_native_app_developer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bootstrap Academy Frontend 2 | 3 | The official frontend of [Bootstrap Academy](https://bootstrap.academy/). 4 | 5 | If you would like to submit a bug report or feature request, or are looking for general information about the project or the publicly available instances, please refer to the [Bootstrap-Academy repository](https://github.com/Bootstrap-Academy/Bootstrap-Academy). 6 | 7 | ## Development Setup 8 | 9 | 1. Install [Node.js and npm](https://nodejs.org/) 10 | 2. Clone this repository and `cd` into it. 11 | 3. Run `npm install` to install the dependencies. 12 | 4. Run `npm run dev` to start a development server listening on http://localhost:3000/. 13 | 14 | ## Code Quality 15 | 16 | - Run `npm run format` to apply the enforced Prettier style (CI runs `npm run format:check`). 17 | - Run `npm run lint` to ensure the code passes the ESLint rules. 18 | 19 | ## Note on Account Creation 20 | 21 | You need to create separate accounts for test instances (localhost, [https://test.bootstrap.academy](https://test.bootstrap.academy), PullRequest-preview pages) and live instances ([https://bootstrap.academy](https://bootstrap.academy)). These are two separate database systems. 22 | -------------------------------------------------------------------------------- /public/svgs/html_und_css.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /pages/auth/reset-password.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 37 | 38 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /components/skeleton/List.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | 32 | 53 | -------------------------------------------------------------------------------- /pages/auth/forgot-password.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 38 | 39 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /components/leaderboard/ChallengeBasedListDialog.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /public/svgs/dart_und_flutter_experte.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /components/svg/Level7Icon.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /pages/challenges/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 32 | 33 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /public/svgs/c_plus_plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /components/course/CardSkeleton.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /pages/morphcoins/error.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /components/hero/FAQS.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /components/user/RefillHeartBtn.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /components/skeleton/SolveMatching.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /composables/mfa.ts: -------------------------------------------------------------------------------- 1 | export async function initializeMFA() { 2 | const user = useUser(); 3 | const userID = user?.value?.id ?? ""; 4 | 5 | try { 6 | if (!!!userID) { 7 | throw { data: { detail: "Missing user id" } }; 8 | } 9 | 10 | const response = await POST(`/auth/users/${userID}/mfa`); 11 | 12 | return [response, null]; 13 | } catch (error: any) { 14 | return [null, error.data]; 15 | } 16 | } 17 | 18 | export async function enableMFA(body: any) { 19 | const user = useUser(); 20 | const userID = user?.value?.id ?? ""; 21 | 22 | try { 23 | if (!!!userID) { 24 | throw { data: { detail: "Missing user id" } }; 25 | } 26 | 27 | const response = await PUT(`/auth/users/${userID}/mfa`, body); 28 | 29 | return [response, null]; 30 | } catch (error: any) { 31 | return [null, error.data]; 32 | } 33 | } 34 | 35 | export async function disableMFA() { 36 | const user = useUser(); 37 | const userID = user?.value?.id ?? ""; 38 | 39 | try { 40 | if (!!!userID) { 41 | throw { data: { detail: "Missing user id" } }; 42 | } 43 | 44 | const response = await DELETE(`/auth/users/${userID}/mfa`); 45 | 46 | return [response, null]; 47 | } catch (error: any) { 48 | return [null, error.data]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /components/user/Coins.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /components/hero/Landing.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /components/MarkdownEditor.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /pages/account/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 36 | 37 | 55 | -------------------------------------------------------------------------------- /pages/account/mfa/disabled.vue: -------------------------------------------------------------------------------- 1 | 17 | 26 | 27 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /composables/coaching.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "#app"; 2 | 3 | export const useCoachings = () => useState("coachings", () => []); 4 | 5 | export async function getCoachingsForThisSubSkill(subSkillID: string) { 6 | try { 7 | if (!!!subSkillID) { 8 | throw { data: { detail: "Invalid sub skill ID" } }; 9 | } 10 | 11 | const response = await GET(`/events/calendar?type=coaching&skill_id=${subSkillID}`); 12 | 13 | const coachings = useCoachings(); 14 | coachings.value = response?.events ?? []; 15 | 16 | if (coachings.value.length > 0) { 17 | coachings.value = coachings.value.sort(function (x, y) { 18 | return x.start - y.start; 19 | }); 20 | } 21 | 22 | return [coachings.value, null]; 23 | } catch (error: any) { 24 | return [null, error.data]; 25 | } 26 | } 27 | 28 | export async function bookCoachingForThisSubSkillWithThisInstructor( 29 | subSkillID: string, 30 | slot_id: string 31 | ) { 32 | try { 33 | if (!!!subSkillID) { 34 | throw { data: { detail: "Invalid sub skill ID" } }; 35 | } 36 | 37 | if (!!!slot_id) { 38 | throw { data: { detail: "Invalid instructor ID" } }; 39 | } 40 | 41 | const response = await POST(`/events/coachings/${subSkillID}/${slot_id}`); 42 | await getBalance(); 43 | return [response, null]; 44 | } catch (error: any) { 45 | return [null, error.data]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /composables/premiumFeature.ts: -------------------------------------------------------------------------------- 1 | export const usePremiumInfo = () => useState("premiumInfo", () => null); 2 | 3 | export const usePremiumPlans = () => useState("premiumPlans", () => []); 4 | 5 | export async function getPremiumPlans() { 6 | try { 7 | const res = await PUT(`/shop/premium_plans`); 8 | const premiumPlans = usePremiumPlans(); 9 | premiumPlans.value = res ?? []; 10 | return [res, null]; 11 | } catch (error: any) { 12 | return [null, error]; 13 | } 14 | } 15 | export async function getPremiumStatus() { 16 | try { 17 | const user: any = useUser(); 18 | const res = await GET(`/shop/premium/${user.value.id}`); 19 | const premiumInfo = usePremiumInfo(); 20 | premiumInfo.value = res ?? null; 21 | return [res, null]; 22 | } catch (error: any) { 23 | return [null, error]; 24 | } 25 | } 26 | 27 | export async function buyPremium(body: any) { 28 | try { 29 | console.log("body", body); 30 | const res = await POST(`/shop/premium`, body); 31 | await getPremiumStatus(); 32 | await getBalance(); 33 | return [res, null]; 34 | } catch (error: any) { 35 | return [null, error]; 36 | } 37 | } 38 | 39 | export async function updatePremiumAutoPay(body: any) { 40 | try { 41 | const res = await PUT(`/shop/premium/autopay`, body); 42 | return [res, null]; 43 | } catch (error: any) { 44 | return [null, error]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /components/subscription/Timer.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 50 | -------------------------------------------------------------------------------- /types/calenderTypes.ts: -------------------------------------------------------------------------------- 1 | export class Calendar { 2 | ics_token: string = ""; 3 | events: WebinarEvent[] | CoachingEvent[] = []; 4 | } 5 | 6 | export class EventBase { 7 | id: string = ""; 8 | type: "webinar" | "coaching" = "webinar"; // "coaching" | "webinar" 9 | title: string = ""; 10 | description: string = ""; 11 | skill_id: string = ""; 12 | start: number = 0; 13 | duration: number = 0; 14 | price: number = 0; 15 | instructor: Instructor = new Instructor(); 16 | instructor_rating: null | number = null; 17 | booked: boolean = false; // "sold-out"? 18 | bookable: boolean = false; 19 | admin_link: string = ""; 20 | link: string = ""; 21 | } 22 | 23 | export class Instructor { 24 | avatar_url: string = ""; 25 | display_name: string = ""; 26 | email: string = ""; 27 | id: string = ""; 28 | name: string = ""; 29 | } 30 | 31 | export class WebinarEvent extends EventBase { 32 | type: "webinar" = "webinar"; 33 | creation_date: number = 0; 34 | max_participants: number = 0; 35 | participants: number = 0; 36 | } 37 | 38 | export class CoachingEvent extends EventBase { 39 | type: "coaching" = "coaching"; 40 | student: { 41 | id: string; 42 | name: string; 43 | display_name: string; 44 | email: string; 45 | avatar_url: string; 46 | } = { 47 | id: "", 48 | name: "", 49 | display_name: "", 50 | email: "", 51 | avatar_url: "", 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /components/icon/Discord.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /components/leaderboard/TopUserCard.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /components/icon/Loading.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /composables/calendar.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "#app"; 2 | import { WebinarEvent, CoachingEvent, Calendar } from "~/types/calenderTypes"; 3 | 4 | export const useCalendar = () => useState("calendar", () => new Calendar()); 5 | export const useICS = () => useState("ics", () => ""); 6 | export const useEvents = () => useState<(WebinarEvent | CoachingEvent)[]>("events", () => []); 7 | export const useEventFilter = () => useState("eventFilter", () => "all"); 8 | 9 | export async function getCalendar() { 10 | try { 11 | const response: Calendar = await GET(`/events/calendar`); 12 | 13 | const calendar = useCalendar(); 14 | calendar.value = response ?? null; 15 | 16 | const ics = useICS(); 17 | const config = useRuntimeConfig().public; 18 | ics.value = `${config.BASE_API_URL}/events/calendar/${response?.ics_token ?? ""}/academy.ics`; 19 | 20 | const events = useEvents(); 21 | events.value = response?.events ?? []; 22 | 23 | return [response, null]; 24 | } catch (error: any) { 25 | return [null, error.data]; 26 | } 27 | } 28 | 29 | export async function cancelCalendarEvent(id: string) { 30 | try { 31 | if (!!!id) { 32 | throw { data: "Invalid event ID" }; 33 | } 34 | 35 | const response = await DELETE(`/events/calendar/${id}`); 36 | getCalendar(); 37 | return [response, null]; 38 | } catch (error: any) { 39 | return [null, error.data]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /public/svgs/c_sharp_grundlagen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/svgs/c_sharp_fuer_fortgeschrittene.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /components/Sort.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /public/svgs/grundlagen_der_it_sicherheit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/svgs/php_grundlagen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /components/Language.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /components/List.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 41 | 42 | 63 | -------------------------------------------------------------------------------- /components/skeleton/CodingChallengeList.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /components/svg/Level3Icon.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /components/challenges/ProgressSummary.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 54 | -------------------------------------------------------------------------------- /pages/auth/signup.vue: -------------------------------------------------------------------------------- 1 | 20 | 43 | 44 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /components/input/RadioGroup.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /components/course/CardSm.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /components/icon/Google.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /pages/profile/edit.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 32 | 33 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /components/skeleton/FormCodingChallenge.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /components/icon/Github.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/svgs/it_forensiker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /components/svg/Level5Icon.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /components/skill-tree/Pathway.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 56 | 57 | 65 | -------------------------------------------------------------------------------- /pages/webinars/create/[skill].vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 37 | 38 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /components/SubMenu.vue: -------------------------------------------------------------------------------- 1 | hear 2 | 30 | 31 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /components/course/Details.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /composables/coins.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "#app"; 2 | import { useUser } from "./user"; 3 | export const useCoins = () => useState("coins", () => 0); 4 | export const usePaypalClientID = () => useState("paypalClientID", () => ""); 5 | 6 | export async function getBalance() { 7 | const user = useUser(); 8 | 9 | try { 10 | if (!!!user.value || !!!user.value.id) { 11 | throw { data: "Missing user id" }; 12 | } 13 | 14 | const response = await GET(`/shop/coins/${user.value.id}`); 15 | 16 | const coins = useCoins(); 17 | coins.value = response?.coins ?? 0; 18 | 19 | return [response, null]; 20 | } catch (error: any) { 21 | return [null, error.data]; 22 | } 23 | } 24 | 25 | export async function getPaypalClientID() { 26 | try { 27 | const response = await GET(`/shop/coins/paypal`); 28 | 29 | const paypalClientID = usePaypalClientID(); 30 | paypalClientID.value = response ?? ""; 31 | 32 | return [response, null]; 33 | } catch (error: any) { 34 | return [null, error.data]; 35 | } 36 | } 37 | 38 | export async function createPaypalOrder(body: any) { 39 | try { 40 | const response = await POST(`/shop/coins/paypal/orders`, body); 41 | 42 | return [response, null]; 43 | } catch (error: any) { 44 | return [null, error.data]; 45 | } 46 | } 47 | 48 | export async function onApproveCapturePaypalOrder(orderID: string) { 49 | try { 50 | if (!!!orderID) { 51 | throw { data: "Missing order id" }; 52 | } 53 | 54 | const response = await POST(`/shop/coins/paypal/orders/${orderID}/capture`); 55 | 56 | return [response, null]; 57 | } catch (error: any) { 58 | return [null, error.data]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /components/icon/Instagram.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /composables/response.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "#app"; 2 | 3 | export const useLoading = () => useState("loading", () => false); 4 | 5 | export function setLoading(status: boolean) { 6 | const loading = useLoading(); 7 | loading.value = status; 8 | } 9 | 10 | export const useSnackbar = () => 11 | useState("snackbar", () => { 12 | return { 13 | show: false, 14 | type: "info", 15 | heading: "", 16 | body: "", 17 | }; 18 | }); 19 | 20 | export function openSnackbar( 21 | type: string, 22 | heading: string, 23 | body: string = "", 24 | noTimeout?: boolean 25 | ) { 26 | const snackbar = useSnackbar(); 27 | snackbar.value = { 28 | show: true, 29 | type, 30 | heading, 31 | body, 32 | }; 33 | 34 | if (!!!noTimeout) { 35 | setTimeout(() => { 36 | closeSnackbar(); 37 | }, 5000); 38 | } 39 | } 40 | 41 | export function closeSnackbar() { 42 | const snackbar = useSnackbar(); 43 | snackbar.value = { 44 | show: false, 45 | type: "info", 46 | heading: "", 47 | body: "", 48 | }; 49 | } 50 | 51 | export const useDialog = () => 52 | useState("dialog", (): any => { 53 | return null; 54 | }); 55 | 56 | export function openDialog( 57 | type: string, 58 | heading: string, 59 | body: string, 60 | triggerPrimaryActionOnBackdropClick: boolean, 61 | primaryBtn: any, 62 | secondaryBtn: any 63 | ) { 64 | const dialog = useDialog(); 65 | dialog.value = { 66 | show: true, 67 | type, 68 | heading, 69 | body, 70 | triggerPrimaryActionOnBackdropClick, 71 | primaryBtn, 72 | secondaryBtn, 73 | }; 74 | } 75 | 76 | export function closeDialog() { 77 | const dialog = useDialog(); 78 | dialog.value = null; 79 | } 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "nuxt build", 5 | "dev": "nuxt dev --open", 6 | "start": "nuxt start", 7 | "generate": "nuxt generate", 8 | "preview": "nuxt preview", 9 | "postinstall": "nuxt prepare", 10 | "lint": "eslint --ext .js,.vue,.ts .", 11 | "lint:fix": "eslint --ext .js,.vue,.ts . --fix", 12 | "format": "prettier --cache --cache-strategy metadata --write \"**/*.{js,ts,vue,json,md,css,scss,html}\"", 13 | "format:check": "prettier --check \"**/*.{js,ts,vue,json,md,css,scss,html}\"" 14 | }, 15 | "devDependencies": { 16 | "@types/dompurify": "^3.2.0", 17 | "@types/katex": "^0.16.7", 18 | "@types/markdown-it": "^14.1.2", 19 | "autoprefixer": "^10.4.23", 20 | "eslint": "^9.39.2", 21 | "eslint-config-prettier": "^10.1.8", 22 | "eslint-plugin-vue": "^10.6.2", 23 | "prettier": "^3.7.4", 24 | "prettier-plugin-tailwindcss": "^0.7.2", 25 | "nuxt": "4.2.2", 26 | "postcss": "^8.5.6", 27 | "tailwindcss": "^3.4.19", 28 | "vue-i18n": "^11.2.2" 29 | }, 30 | "dependencies": { 31 | "@floating-ui/dom": "^1.7.4", 32 | "@headlessui/vue": "^1.7.23", 33 | "@heroicons/vue": "^2.2.0", 34 | "@panzoom/panzoom": "^4.6.1", 35 | "@typescript-eslint/parser": "^8.49.0", 36 | "@vueuse/core": "^14.1.0", 37 | "async-mutex": "^0.5.0", 38 | "dompurify": "^3.3.1", 39 | "highlight.js": "^11.11.1", 40 | "i": "^0.3.7", 41 | "jwt-decode": "^4.0.0", 42 | "katex": "^0.16.27", 43 | "markdown-it": "^14.1.0", 44 | "monaco-editor": "^0.55.1", 45 | "nanoid": "^5.1.6", 46 | "npm": "^11.7.0", 47 | "qrcode.vue": "^3.6.0", 48 | "vue-recaptcha-v3": "^2.0.1", 49 | "vuedraggable": "^4.1.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /components/job/CardSkeleton.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /components/challenges/ItemLimits.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /pages/calendar.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 38 | 39 | 73 | 74 | 79 | -------------------------------------------------------------------------------- /components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 49 | 50 | 58 | -------------------------------------------------------------------------------- /public/svgs/grundlagen_der_programmierung_und_datenhaltung.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/svgs/uml.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /components/subscription/PremiumUntillCountDown.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /components/svg/Level4Icon.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/svgs/datenschutz_und_privacy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /pages/account/mfa/disable.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 42 | 43 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /components/skill-tree/ZoomLevel.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /components/account/VerifyEmail.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /pages/challenges/all.vue: -------------------------------------------------------------------------------- 1 | 21 | 35 | 36 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /components/user/Progress.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /composables/newsletter.ts: -------------------------------------------------------------------------------- 1 | export async function requestNewsletterRegistration() { 2 | const user = useUser(); 3 | let user_id = user?.value?.id ?? null; 4 | 5 | try { 6 | if (!!!user_id) { 7 | throw { data: "Invalid User Id" }; 8 | } 9 | 10 | const response = await PATCH(`/auth/users/${user_id}`, { 11 | newsletter: true, 12 | }); 13 | 14 | const user = useUser(); 15 | const cookie_user = useCookie("user"); 16 | user.value = response ?? null; 17 | cookie_user.value = user.value; 18 | 19 | return [response, null]; 20 | } catch (error: any) { 21 | return [null, error.data]; 22 | } 23 | } 24 | 25 | export async function registerForNewsletter(body: any) { 26 | const user = useUser(); 27 | let user_id = user?.value?.id ?? null; 28 | 29 | try { 30 | if (!!!user_id) { 31 | throw { data: "Invalid User Id" }; 32 | } 33 | 34 | const response = await PUT(`/auth/users/${user_id}/newsletter`, body); 35 | 36 | const user = useUser(); 37 | const cookie_user = useCookie("user"); 38 | user.value = response ?? null; 39 | cookie_user.value = user.value; 40 | 41 | return [response, null]; 42 | } catch (error: any) { 43 | return [null, error.data]; 44 | } 45 | } 46 | 47 | export async function unregisterFromNewsletter() { 48 | const user = useUser(); 49 | let user_id = user?.value?.id ?? null; 50 | 51 | try { 52 | if (!!!user_id) { 53 | throw { data: "Invalid User Id" }; 54 | } 55 | 56 | const response = await PATCH(`/auth/users/${user_id}`, { 57 | newsletter: false, 58 | }); 59 | 60 | const user = useUser(); 61 | const cookie_user = useCookie("user"); 62 | user.value = response ?? null; 63 | cookie_user.value = user.value; 64 | 65 | return [response, null]; 66 | } catch (error: any) { 67 | return [null, error.data]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pages/webinars/[id].vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 37 | 38 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /pages/webinars/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 41 | 42 | 66 | 67 | 78 | -------------------------------------------------------------------------------- /public/svgs/tensorflow_mit_python.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /description.ts: -------------------------------------------------------------------------------- 1 | export const description = 2 | "most challenges, the goal is not only to write a program that produces a certain output for a given input, but also to make this program as efficient as possible. Only a limited time is available for the execution of the program (see below). If the program is not finished after this time limit (in this introduction challenge one second), the corresponding test case and thus the subtask is considered as failed. In this challenge, the task is to calculate the sum of the first ***n*** natural numbers for a given natural number ***n***. \n\n
\n\nA possible solution for this is to iterate over the first ***n*** natural numbers, adding them together. However, the execution time of this solution is proportional to ***n***, which becomes problematic in the second sub-task, since up to two billion numbers would have to be added, which would take an extremely long time. A better solution here would be, for example, the Gaussian summation formula:\n\n
\n\n$$sum_{k=1}^{n}k=\\frac{n(n+1)}{2}$$\n\n
\n\nwhich can be used to calculate the sum of the first ***n*** natural numbers in constant time. \n\n**Note**: When implementing the Gaussian summation formula, it is essential to make sure that you exclusively use integers and not floats in your calculations, since the latter are too imprecise for the numbers relevant in Task 2. A frequently occurring problem, especially in Python, is that dividing by 2 results in a float at the end, which is unfortunately too inaccurate. Instead of dividing by 2 using the `/` operator (float division) and then converting the float into an integer, the `//` operator (integer division) can be used, which directly returns an integer when dividing, whereby no precision should be lost.\n\n
\n\n### Input:\n- Line 1: The number ***n*** \n\n### Output:\n- Line 1: The sum of the first ***n*** natural numbers $$sum_{k=1}^{n}k$$\n\n```JAVASCRIPT\nlet a = 'hello world'\nconsole.log(a)\n```"; 3 | -------------------------------------------------------------------------------- /components/skeleton/Text.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 52 | 53 | 74 | -------------------------------------------------------------------------------- /public/svgs/vernetzte_systeme.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/svgs/typescript.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /components/account/MFA.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 55 | --------------------------------------------------------------------------------