Merge pull request #1328 from microsoft/upgrade-quiz-app

upgrading app to vue 3
dependabot/npm_and_yarn/5-browser-extension/start/webpack-5.94.0
chris 3 months ago committed by GitHub
commit d0cf3ac4eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,11 @@
/* eslint-env node */
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

@ -1,8 +0,0 @@
module.exports = {
extends: [
'plugin:vue/vue3-recommended',
],
rules: {
'vue/multi-word-component-names': 'off'
}
}

@ -1 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.vscode
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

@ -1,5 +0,0 @@
{
"staticWebApps.appSubpath": "/",
"staticWebApps.apiSubpath": "api",
"staticWebApps.outputSubpath": "dist"
}

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Arpan Adhikari
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -1,113 +1,35 @@
# Quizzes
# Quiz app
These quizzes are the pre- and post-lecture quizzes for the web development for beginners curriculum at https://aka.ms/webdev-beginners
This quiz app helps you display questions and answers in a quiz format.
## Project setup
## Install the app
```
Run `npm install` to install the dependencies.
```sh
npm install
```
### Compiles and hot-reloads for development
## Build the app
```
npm run serve
```
To build the solution, run `npm run build`.
### Compiles and minifies for production
```
```sh
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
Credits: Thanks to the original version of this quiz app: https://github.com/arpan45/simple-quiz-vue
## Run
## Deploying to Azure
To run the solution, run `npm run dev`.
Heres a step-by-step guide to help you get started:
1. Fork the a GitHub Repository
Ensure your static web app code is in your GitHub repository. Fork this repository.
2. Create an Azure Static Web App
- Create and [Azure account](http://azure.microsoft.com)
- Go to the [Azure portal](https://portal.azure.com)
- Click on “Create a resource” and search for “Static Web App”.
- Click “Create”.
3. Configure the Static Web App
- Basics: Subscription: Select your Azure subscription.
- Resource Group: Create a new resource group or use an existing one.
- Name: Provide a name for your static web app.
- Region: Choose the region closest to your users.
- #### Deployment Details:
- Source: Select “GitHub”.
- GitHub Account: Authorize Azure to access your GitHub account.
- Organization: Select your GitHub organization.
- Repository: Choose the repository containing your static web app.
- Branch: Select the branch you want to deploy from.
- #### Build Details:
- Build Presets: Choose the framework your app is built with (e.g., React, Angular, Vue, etc.).
- App Location: Specify the folder containing your app code (e.g., / if its in the root).
- API Location: If you have an API, specify its location (optional).
- Output Location: Specify the folder where the build output is generated (e.g., build or dist).
4. Review and Create
Review your settings and click “Create”. Azure will set up the necessary resources and create a GitHub Actions workflow in your repository.
5. GitHub Actions Workflow
Azure will automatically create a GitHub Actions workflow file in your repository (.github/workflows/azure-static-web-apps-<name>.yml). This workflow will handle the build and deployment process.
6. Monitor the Deployment
Go to the “Actions” tab in your GitHub repository.
You should see a workflow running. This workflow will build and deploy your static web app to Azure.
Once the workflow completes, your app will be live on the provided Azure URL.
### Example Workflow File
Heres an example of what the GitHub Actions workflow file might look like:
name: Azure Static Web Apps CI/CD
```bash
npm run dev
```
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
jobs:
build_and_deploy_job:
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
- uses: actions/checkout@v2
- name: Build And Deploy
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: "upload"
app_location: "/quiz-app # App source code path"
api_location: ""API source code path optional
output_location: "dist" #Built app content directory - optional
```
## Lint
### Additional Resources
- [Azure Static Web Apps Documentation](https://learn.microsoft.com/azure/static-web-apps/getting-started)
- [GitHub Actions Documentation](https://docs.github.com/actions/use-cases-and-examples/deploying/deploying-to-azure-static-web-app)
To ensure the code is linted, run `npm run lint`.
```bash
npm run lint
```

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

File diff suppressed because it is too large Load Diff

@ -1,48 +1,22 @@
{
"name": "quizzes",
"version": "0.1.0",
"name": "quizapp",
"version": "1.0.0",
"private": true,
"engines": {
"npm": ">=9.0.0",
"node": ">=18.0.0"
},
"type": "module",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-i18n": "^8.28.2",
"vue-router": "^3.2.0"
"vue": "^3.4.29",
"vue-router": "^4.3.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-service": "~5.0.8",
"babel-eslint": "^10.1.0",
"eslint": "^7.5.0",
"eslint-plugin-vue": "^9.14.1",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
"@vitejs/plugin-vue": "^5.0.5",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"vite": "^5.3.1"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

@ -1,8 +0,0 @@
{
"routes": [
{
"route": "/*",
"serve": "/index.html"
}
]
}

@ -1,116 +1,84 @@
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import QuizGreeting from './components/QuizGreeting.vue'
</script>
<template>
<div>
<nav>
<router-link
class="navlink"
to="/"
>
Home
</router-link>
<label for="locale">locale</label>
<select v-model="locale">
<option
v-for="localeName in availableLocales"
:key="localeName"
>
{{ localeName }}
</option>
</select>
</nav>
<div id="app">
<h1>{{ $t("title") }}</h1>
<router-view>
<Quiz />
</router-view>
<header>
<div class="wrapper">
<QuizGreeting msg="Quiz app" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
</div>
</div>
</template>
</header>
<script>
import Quiz from "@/components/Quiz.vue";
import messages from "@/assets/translations";
<RouterView />
</template>
export default {
name: "App",
i18n: { messages },
components: {
Quiz,
},
data() {
return {
locale: "en",
availableLocales: Object.keys(messages).sort(),
};
},
watch: {
locale(val) {
this.$root.$i18n.locale = val;
},
},
created() {
if (this.$route.query.loc) {
this.locale = this.$route.query.loc;
}
},
};
</script>
<style scoped>
header {
line-height: 1.5;
max-height: 100vh;
}
<style>
html {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #252d4a;
.logo {
display: block;
margin: 0 auto 2rem;
}
nav {
background-color: #252d4a;
padding: 1em;
margin-bottom: 20px;
width: 100%;
font-size: 12px;
text-align: center;
margin-top: 2rem;
}
nav a {
color: white;
text-align: right;
nav a.router-link-exact-active {
color: var(--color-text);
}
.link {
display: list-item;
nav a.router-link-exact-active:hover {
background-color: transparent;
}
h1,
h2,
h3,
.message {
text-align: center;
}
.error {
color: red;
}
.card {
width: 60%;
border: #252d4a solid;
border-radius: 5px;
margin: auto;
padding: 1em;
nav a {
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
.btn {
min-width: 50%;
font-size: 16px;
text-align: center;
cursor: pointer;
margin-bottom: 5px;
width: 50%;
font-size: 16px;
color: #ffffff;
background-color: #252d4a;
border-radius: 5px;
padding: 5px;
justify-content: flex-start;
align-items: center;
nav a:first-of-type {
border: 0;
}
.ans-btn {
justify-content: center;
display: flex;
margin: 4px auto;
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
}
</style>

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

@ -0,0 +1,35 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}

@ -1,84 +0,0 @@
<template>
<div class="card">
<div
v-for="q in questions"
:key="q.id"
>
<div v-if="route == q.id">
<h2>{{ q.title }}</h2>
<hr>
<h3
v-if="complete"
class="message"
>
{{ $t("complete") }}
</h3>
<div v-else>
<h3
v-if="error"
class="error"
>
{{ $t("error") }}
</h3>
<h2>
{{ q.quiz[currentQuestion].questionText }}
</h2>
<div>
<button
v-for="(option, index) in q.quiz[currentQuestion].answerOptions"
:key="index"
class="btn ans-btn"
@click="handleAnswerClick(option.isCorrect)"
>
{{ option.answerText }}
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import messages from "@/assets/translations";
export default {
name: "Quiz",
data() {
return {
currentQuestion: 0,
complete: false,
error: false,
route: "",
locale: "",
};
},
computed: {
questions() {
return this.$t("quizzes");
},
},
i18n: { messages },
created() {
this.route = this.$route.params.id;
this.locale = this.$route.query.loc;
},
methods: {
handleAnswerClick(isCorrect) {
this.error = false;
let nextQuestion = this.currentQuestion + 1;
if (isCorrect == "true") {
//always 3 questions per quiz
if (nextQuestion < 3) {
this.currentQuestion = nextQuestion;
} else {
this.complete = true;
}
} else {
this.error = true;
}
},
},
};
</script>

@ -0,0 +1,128 @@
<style scoped>
.button {
margin: 10px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.button:hover {
background-color: black;
color: white;
}
.error {
color: red;
background-color: pink;
}
</style>
<template>
<div class="card">
Back to <router-link to="/">Quizzes</router-link>
</div>
<div class="card">
<div
v-for="q in questions"
:key="q.id"
>
<div v-if="route == q.id">
<h2>{{ q.title }}</h2>
<hr>
<h3
v-if="complete"
class="message"
>
{{ file.complete }}
</h3>
<div v-else>
<h3
v-if="error"
class="error"
>
{{ file.error }}
</h3>
<h2>
{{ q.quiz[currentQuestion].questionText }}
</h2>
<div>
<button
v-for="(option, index) in q.quiz[currentQuestion].answerOptions"
:key="index"
class="btn ans-btn button"
@click="handleAnswerClick(option.isCorrect)"
>
{{ option.answerText }}
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
function getFileByLocale(locale) {
// loop keys in messages, if key is the start of locale, return that file
let file = {
complete: "Complete",
error: "Error",
quizzes: [],
title: "Default Quiz",
};
for (let key in messages) {
if (locale.startsWith(key)) {
file = messages[key];
break;
}
}
return file;
}
import messages from "@/assets/translations";
let locale = navigator.language || navigator.userLanguage;
console.log("locale", locale);
console.log("messages", messages);
export default {
name: "QuizComponent",
data() {
return {
currentQuestion: 0,
complete: false,
error: false,
route: "",
locale: "",
file: getFileByLocale(locale),
};
},
computed: {
questions() {
return this.file.quizzes;
},
},
created() {
this.route = this.$route.params.id;
this.locale = this.$route.query.loc;
},
methods: {
handleAnswerClick(isCorrect) {
this.error = false;
let nextQuestion = this.currentQuestion + 1;
if (isCorrect == "true") {
//always 3 questions per quiz
if (nextQuestion < 3) {
this.currentQuestion = nextQuestion;
} else {
this.complete = true;
}
} else {
this.error = true;
}
},
},
};
</script>

@ -0,0 +1,42 @@
<script setup>
defineProps({
msg: {
type: String,
required: true
}
})
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Welcome to the quiz app! This is a simple quiz app that allows you to take quizzes and test your knowledge.
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

@ -0,0 +1,59 @@
<style>
.question {
margin: 10px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
</style>
<template>
<div class="question" v-for="q in questions" :key="q.id" >
<router-link
:key="q.id"
:to="`quiz/${q.id}`"
class="link"
>
{{ q.title }}
</router-link>
</div>
</template>
<script>
import messages from "@/assets/translations";
function getFileByLocale(locale) {
// loop keys in messages, if key is the start of locale, return that file
let file = {
complete: "Complete",
error: "Error",
quizzes: [],
title: "Default Quiz",
};
for (let key in messages) {
if (locale.startsWith(key)) {
file = messages[key];
break;
}
}
return file;
}
let locale = navigator.language || navigator.userLanguage;
export default {
name: "QuizzesComponent",
data() {
return {
file: getFileByLocale(locale),
};
},
computed: {
questions() {
return this.file.quizzes;
},
},
i18n: { messages },
};
</script>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

@ -1,14 +1,11 @@
import Vue from 'vue';
import App from './App.vue';
Vue.config.productionTip = false;
import router from './router';
import './assets/main.css'
import VueI18n from 'vue-i18n';
Vue.use(VueI18n);
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const i18n = new VueI18n({
locale: 'en',
fallbackLocale: 'en',
});
const app = createApp(App)
new Vue({ i18n, router, render: (h) => h(App) }).$mount('#app');
app.use(router)
app.mount('#app')

@ -1,34 +1,29 @@
import Vue from 'vue';
import Router from 'vue-router';
import Home from '@/views/Home.vue';
import Quiz from '@/components/Quiz.vue';
import NotFound from '@/views/NotFound.vue';
Vue.use(Router);
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import QuizComponent from '@/components/QuizComponent.vue'
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/quiz/:id',
name: 'Quiz',
component: Quiz,
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound,
meta: { title: 'Not Found' },
},
],
});
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/quiz/:id',
name: 'quiz-detail',
component: QuizComponent
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue')
}
]
})
router.beforeEach((to, from, next) => {
next();
});
export default router;
export default router

@ -0,0 +1,16 @@
<template>
<div class="about">
<h1>Quiz app</h1>
This is the about page for the quiz app.
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>

@ -1,26 +0,0 @@
<template>
<div>
<router-link
v-for="q in questions"
:key="q.id"
:to="`quiz/${q.id}`"
class="link"
>
{{ q.title }}
</router-link>
</div>
</template>
<script>
import messages from "@/assets/translations";
export default {
name: "Home",
computed: {
questions() {
return this.$t("quizzes");
},
},
i18n: { messages },
};
</script>

@ -0,0 +1,9 @@
<script setup>
import QuizzesComponent from '@/components/QuizzesComponent.vue';
</script>
<template>
<main>
<QuizzesComponent />
</main>
</template>

@ -1,5 +0,0 @@
<template>
<div>
<p>Sorry, wrong number!</p>
</div>
</template>

@ -0,0 +1,16 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
Loading…
Cancel
Save