Commit 6f05a0fd authored by Nurasyl's avatar Nurasyl

Initial commit

parents
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
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
{
"recommendations": ["Vue.volar"]
}
# Lesson-2
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
/// <reference types="vite/client" />
<!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.ts"></script>
</body>
</html>
This diff is collapsed.
{
"name": "lesson-2",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force"
},
"dependencies": {
"axios": "^1.6.8",
"vue": "^3.4.21",
"vue-router": "^4.3.2"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.12.5",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/tsconfig": "^0.5.1",
"npm-run-all2": "^6.1.2",
"typescript": "~5.4.0",
"vite": "^5.2.8",
"vue-tsc": "^2.0.11"
}
}
<script lang="ts">
export default {
}
</script>
<template>
<main>
<RouterView></RouterView>
</main>
</template>
<style scoped>
header {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
nav {
display: flex;
align-items: center;
justify-content: center;
}
</style>
<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>
<script lang="ts">
export default {
props: {
id: {
type: Number,
required: true,
default: ""
},
img: {
type: String,
required: true,
default: ""
},
pokemonName: {
type: String,
required: true,
default: ""
},
clickPokemon: {
type: Function,
required: true
}
}
}
</script>
<template>
<div class="pokemonCard" @click="clickPokemon(id)">
<div class="pokemonCard_img">
<img :src="img" alt="pokemon">
</div>
<h2 class="pokemonCard_name">
{{ pokemonName }}
</h2>
</div>
</template>
<style scoped src="../styles/pokemonCard.css"></style>
<script lang="ts">
import { useRouter } from 'vue-router';
import { defineComponent } from 'vue';
export default defineComponent({
props: {
pokemon: {
type: Object
},
closePopUp: {
type: Function,
required: true
}
},
setup(props) {
const router = useRouter();
const selectClick = () => {
router.push(`/pokemonGame/${props.pokemon?.id}`)
};
return {
selectClick
}
}
})
</script>
<template>
<div class="PopUp">
<div class="BackDrop" @click="closePopUp()"></div>
<div class="pokemonCard_img">
<img :src="pokemon?.sprites?.front_default" alt="pokemon">
<button class="select_btn" @click="selectClick">Select</button>
</div>
</div>
</template>
<style scoped src="../styles/popUp.css"></style>
<script lang="ts">
export default {
}
</script>
<template>
<div class="Spinner">
<div class="BackDrop"></div>
<div class="Loader"></div>
</div>
</template>
<style scoped src="../styles/spinner.css"></style>
import axios from "axios";
export const axiosPokemon = axios.create({baseURL: "https://pokeapi.co/api/v2/pokemon/"});
\ No newline at end of file
export const regexUrl = (url: string | null) => {
const regex = /\?(.+)/;
const match = url ? url.match(regex)![0] : null;
return match;
};
\ No newline at end of file
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './styles/main.css'
createApp(App).use(router).mount('#app')
<script lang="ts">
import { useRoute } from 'vue-router';
import { defineComponent } from 'vue';
import { axiosPokemon } from '@/config/axiosPokemon';
import type { TDataGame } from '@/types/AppData';
import Spinner from '@/components/Spinner.vue';
export default defineComponent({
data(): TDataGame {
return {
userPokemon: null,
computerPokemon: null,
isLoading: false
}
},
methods: {
getRandomid() {
const randomId = Math.floor((Math.random() * 280) + 1);
return randomId
},
async getUserPokemon() {
this.isLoading = true;
const route = useRoute();
const pokemonId = route.params.id;
const {data} = await axiosPokemon.get(`${pokemonId}`);
this.userPokemon = data;
this.isLoading = false;
},
async getComputerPokemon() {
this.isLoading = true;
const {data} = await axiosPokemon.get(`${this.getRandomid()}`);
this.computerPokemon = data;
this.isLoading = false;
},
userAttack() {
const copyComp = {...this.computerPokemon!}
const copyUser = {...this.userPokemon!}
copyComp.stats[0].base_stat = copyUser.stats[1].base_stat - copyComp.stats[0].base_stat
this.computerPokemon = copyComp
}
},
mounted() {
this.getUserPokemon()
this.getComputerPokemon()
}
})
</script>
<template>
<Spinner v-show="isLoading"/>
<div class="Game">
<div class="Avatar user_avatar">
<img src="../assets/userAvatar.jpg" alt="avatar">
<h2>User</h2>
<p>Pokemon: {{ userPokemon?.name }}</p>
<div class="Stats">
<span
v-for="el in userPokemon?.stats"
>
{{ el.stat.name }} : {{ el.base_stat }}
</span>
</div>
<div class="Actions">
<button class="btn attack" @click="userAttack">Attack</button>
</div>
</div>
<div class="Avatar computer_avatar">
<img src="../assets/computerAvatar.jpg" alt="avatar">
<h2>Computer</h2>
<p>Pokemon: {{ computerPokemon?.name }}</p>
<div class="Stats">
<span
v-for="el in computerPokemon?.stats"
>
{{ el.stat.name }} : {{ el.base_stat }}
</span>
</div>
</div>
<div class="Pokemon user_pokemon">
<img :src="userPokemon?.sprites.back_default" alt="pokemon">
</div>
<div class="Pokemon computer_pokemon">
<img :src="computerPokemon?.sprites.front_default" alt="pokemon">
</div>
</div>
</template>
<style scoped src="../styles/pokemonGame.css"></style>
<script lang="ts">
import { axiosPokemon } from "../config/axiosPokemon"
import type { TData } from '../types/AppData';
import PokemonCard from '../components/PokemonCard.vue';
import PopUp from '../components/PopUp.vue';
import Spinner from "../components/Spinner.vue";
import { regexUrl } from "../helpers/regexUrl";
export default {
components: {
PokemonCard,
PopUp,
Spinner
},
data(): TData {
return {
pokemons: [],
selectPokemon: null,
next: null,
prev: null,
isLoading: false
}
},
methods: {
async getPokemons(url: string) {
this.isLoading = true;
const {data} = await axiosPokemon.get(url);
const allPokemons = await Promise.all(
data.results.map(async (el: any) => {
const {data} = await axiosPokemon.get(el.url);
return data;
})
);
this.next = regexUrl(data.next);
this.prev = regexUrl(data.previous);
this.pokemons = allPokemons;
this.isLoading = false;
},
onSelectPokemon(id: number) {
const index = this.pokemons.findIndex(el => el.id === id);
this.selectPokemon = this.pokemons[index];
},
onClosePopUp() {
this.selectPokemon = null;
},
onNext() {
this.getPokemons(this.next || "");
},
onPrev() {
this.getPokemons(this.prev || "");
}
},
mounted() {
this.getPokemons("");
},
}
</script>
<template>
<div class="App">
<Spinner v-show="isLoading"/>
<button
:disabled="prev === null"
class="pagination_btn prev_btn"
@click="onPrev"
>
Prev
</button>
<PokemonCard
v-for="el in pokemons"
:id="el.id"
:key="el.id"
:img="el.sprites.front_default"
:pokemon-name="el.name"
:click-pokemon="onSelectPokemon"
/>
<button
:disabled="next === null"
class="pagination_btn next_btn"
@click="onNext"
>
Next
</button>
<PopUp
v-show="selectPokemon !== null"
:pokemon="selectPokemon || {}"
:close-pop-up="onClosePopUp"
/>
</div>
</template>
<style scoped src="../styles/app.css"></style>
\ No newline at end of file
import { createMemoryHistory, createRouter } from "vue-router";
import PokemonList from "./pages/PokemonList.vue";
import PokemonGame from "./pages/PokemonGame.vue";
const routes = [
{
path: "/",
component: PokemonList
},
{
path: "/pokemonGame/:id",
component: PokemonGame
}
];
const router = createRouter({
history: createMemoryHistory(),
routes
})
export default router;
\ No newline at end of file
.App {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15px;
width: 100%;
}
.pagination_btn {
position: absolute;
top: 45%;
border: none;
width: 80px;
height: 40px;
border-radius: 5px;
color: rgb(35, 34, 34);
font-size: 20px;
cursor: pointer;
z-index: 1;
}
.pagination_btn:hover {
box-shadow: 0px 0px 10px 0px white;
}
.pagination_btn:disabled {
background-color: rgb(115, 114, 114);
}
.pagination_btn:disabled:hover {
box-shadow: none;
}
.prev_btn {
left: 30px;
}
.next_btn {
right: 30px;
}
\ No newline at end of file
/* 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;
}
@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;
}
}
.pokemonCard {
height: 150px;
width: 150px;
border-radius: 10px;
background-color: white;
box-shadow: 0px 0px 12px 0px white;
cursor: pointer;
}
.pokemonCard:hover {
box-shadow: 0px 0px 18px 0px white;
}
.pokemonCard_img {
margin: 0 auto;
width: 80%;
height: 70%;
}
.pokemonCard_img > img {
width: 100%;
height: 100%;
}
.pokemonCard_name {
text-align: center;
color: rgb(36, 35, 35);
text-transform: capitalize;
font-size: 20px;
font-weight: 600;
}
\ No newline at end of file
.Game {
background-image: url("../assets/arena01.png");
background-repeat: no-repeat;
background-size: 100% 100%;
height: 550px;
width: 1100px;
}
.Avatar {
top: 200px;
position: absolute;
width: 150px;
height: 150px;
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.Avatar > img {
width: 100%;
height: 100%;
}
.Avatar > h2 {
font-size: 25px;
font-weight: 600;
color: white;
}
.Avatar > p {
margin: 0;
text-transform: capitalize;
color: white;
}
.Avatar > .Stats {
display: flex;
flex-direction: column;
width: 100%;
}
.Avatar > .Stats > span {
text-transform: capitalize;
margin-bottom: 5px;
}
.Avatar > .Actions {
width: 100%;
margin-top: 25px;
}
.Avatar > .Actions > .btn {
width: 100%;
height: 25px;
font-size: 18px;
cursor: pointer;
}
.user_avatar {
left: 30px;
}
.computer_avatar {
right: 30px;
}
.Pokemon {
position: absolute;
width: 300px;
height: 300px;
}
.Pokemon > img {
width: 100%;
height: 100%;
}
.user_pokemon {
left: 23%;
top: 35%;
}
.computer_pokemon {
top: 32%;
right: 25%;
}
\ No newline at end of file
.PopUp {
position: fixed;
inset: 0;
height: 100vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.BackDrop {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
background-color: black;
opacity: 0.7;
z-index: 1;
}
.pokemonCard_img {
position: relative;
width: 300px;
height: 300px;
border-radius: 25px;
background-color: white;
box-shadow: 0px 0px 12px 0px white;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
.pokemonCard_img > img {
width: 100%;
height: 100%;
}
.pokemonCard_img > .select_btn {
position: absolute;
bottom: 10px;
width: 120px;
height: 30px;
border-radius: 8px;
border: none;
background-color: rgb(172, 168, 168);
color: white;
font-size: 20px;
font-weight: 600;
box-shadow: 0px 0px 7px 0px black;
cursor: pointer;
}
.pokemonCard_img > .select_btn:hover {
box-shadow: 0px 0px 12px 0px black;
}
\ No newline at end of file
.Spinner {
position: fixed;
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
inset: 0;
z-index: 10;
}
.BackDrop {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
background-color: black;
opacity: 0.7;
z-index: 4;
}
.Loader {
position: relative;
z-index: 5;
border: 16px solid #f3f3f3; /* Light grey */
border-top: 16px solid #3498db; /* Blue */
border-radius: 50%;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
\ No newline at end of file
export type TPokemonStat = {
base_stat: number,
stat: {
name: string
}
}
export type TPokemon = {
id: number,
name: string,
sprites: {
front_default: string,
back_default: string
},
stats: TPokemonStat[]
}
export type TData = {
pokemons: TPokemon[],
selectPokemon: TPokemon | null,
next: string | null,
prev: string | null,
isLoading: boolean
}
export type TDataGame = {
userPokemon: TPokemon | null,
computerPokemon: TPokemon | null,
isLoading: boolean
}
\ No newline at end of file
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}
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))
}
}
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment