Upgrade to vue 3 (#1)

* updated dependencies

* updated readme

* upgrade to vue 3

* updated buttons

* duplicate id removed

* updated contact
This commit is contained in:
2022-07-13 09:48:44 +02:00
committed by GitHub
parent 0bd0d31947
commit 7475bb5671
36 changed files with 9847 additions and 19845 deletions

View File

@@ -1,29 +0,0 @@
<template>
<button class="btn" :class="{ 'btn-reverse': isReverse }">
<font-awesome-icon class="icon" v-if="hasIcon" :icon="icon"/>
<span><slot></slot></span>
</button>
</template>
<script>
export default {
name: 'BaseButton',
props: {
hasIcon: Boolean,
icon: String,
isReverse: Boolean
}
}
</script>
<style lang="scss" scoped>
@import "scss/_btn";
.btn {
@include button($has-icon: true);
}
.btn-reverse {
@include button(true, true);
}
</style>

View File

@@ -1,27 +1,43 @@
<template>
<footer class="footer">
<div class="footer_container">
<div class="container_logo">Kamil<span class="logo_element">Craft</span></div>
<div class="container_logo">
Kamil<span class="logo_element">Craft</span>
</div>
<ul class="socials">
<li class="social_link"
v-for="link in socialLinks"
:key="link.title.slug">
<a class="link"
:href="link.link"
:title="link.title"
target="_blank"
rel="noopener nofollow noreferrer">
<font-awesome-icon class="icon" :icon="['fab', link.icon]" />
<li
v-for="link in socialLinks"
:key="link.title.slug"
class="social_link"
>
<a
class="link"
:href="link.link"
:title="link.title"
target="_blank"
rel="noopener nofollow noreferrer"
>
<font-awesome-icon
class="icon"
:icon="['fab', link.icon]"
/>
<span class="social_title">{{ link.title }}</span>
<span class="social_shortcut">{{ link.shortcut }}</span>
</a>
</li>
</ul>
</div>
<div class="container"></div>
<div class="container" />
</footer>
</template>
<script setup>
const socialLinks = [
{ link: 'https://www.youtube.com/user/kamilniemczycki', icon: 'youtube', title: 'Oglądaj mnie na YouTube', shortcut: 'YouTube' },
{ link: 'https://www.facebook.com/nikcamii', icon: 'facebook', title: 'Znajdź mnie na Facebooku', shortcut: 'Facebook' }
]
</script>
<style lang="scss" scoped>
@import "scss/default";
@@ -104,17 +120,3 @@
}
}
</style>
<script>
export default {
name: 'Footer',
data () {
return {
socialLinks: [
{ link: 'https://www.youtube.com/user/kamilniemczycki', icon: 'youtube', title: 'Oglądaj mnie na YouTube', shortcut: 'YouTube' },
{ link: 'https://www.facebook.com/nikcamii', icon: 'facebook', title: 'Znajdź mnie na Facebooku', shortcut: 'Facebook' }
]
}
}
}
</script>

View File

@@ -1,27 +0,0 @@
<template>
<button class="btn">
<font-awesome-icon class="icon" v-if="hasIcon" :icon="icon"/>
<span><slot></slot></span>
</button>
</template>
<script>
export default {
name: 'GhostButton',
props: {
hasIcon: {
type: Boolean,
default: false
},
icon: String
}
}
</script>
<style lang="scss" scoped>
@import "scss/btn";
.btn {
@include ghost-button();
}
</style>

View File

@@ -1,10 +1,16 @@
<template>
<div class="hello">
<h1>{{ $route.meta.title }}</h1>
<h1>{{ route.meta.title }}</h1>
<p>Witam na mojej stronie głównej. Wszystkich zainteresowanych oczywiście 😁</p>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
</script>
<style scoped lang="scss">
h1 {
margin: 30px 0 20px;

View File

@@ -1,28 +1,110 @@
<template>
<div class="container" :class="{'container-menu-active': clickedStatus}">
<div
class="container"
:class="{ 'container-menu-active': isClicked }"
>
<router-link to="/">
<div class="logo" @click="linkClicked">Kamil<span class="logo-element">Craft</span></div>
<div
class="logo"
@click="linkClicked"
>
Kamil<span class="logo-element">Craft</span>
</div>
</router-link>
<nav class="nav">
<button class="nav-btn" @click="clickMenu">
<font-awesome-icon v-if="!clicked" icon="bars"></font-awesome-icon>
<font-awesome-icon v-else icon="times"></font-awesome-icon>
<button
class="nav-btn"
@click="clickMenu"
>
<font-awesome-icon
v-if="!isClicked"
icon="bars"
/>
<font-awesome-icon
v-else
icon="times"
/>
</button>
<ul class="site-menu" :class="{'menu-clicked': clicked}">
<li v-for="menuItem in menuItems" :key="menuItem.slug" class="menu-element" @click="linkClicked">
<router-link :to="menuItem.uri">{{ menuItem.title }}</router-link>
<ul
class="site-menu"
:class="{ 'menu-clicked': isClicked }"
>
<li
v-for="menuItem in menuItems"
:key="menuItem.slug"
class="menu-element"
@click="linkClicked"
>
<RouterLink :to="menuItem.uri">
{{ menuItem.title }}
</RouterLink>
</li>
</ul>
</nav>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const menuItems = [
{
slug: 'start',
title: 'Start',
uri: '/'
},
{
slug: 'projects',
title: 'Projekty',
uri: '/projects'
},
{
slug: 'about',
title: 'O mnie',
uri: '/about'
},
{
slug: 'contact',
title: 'Kontakt',
uri: '/contact'
}
]
const clicked = ref(false)
const isClicked = computed(() => clicked.value)
function changeClickedStatus() {
clicked.value = !clicked.value
}
function clickMenu() {
changeClickedStatus()
}
function linkClicked() {
if (isClicked.value) {
changeClickedStatus()
}
}
</script>
<style lang="scss" scoped>
@import "scss/media";
.sub-page > .container .nav-btn,
.sub-page > .container .nav .site-menu .menu-element a:not([class|=router-link-exact]) {
color: #8D8D8D;
.sub-page > .container {
.nav-btn {
color: #8D8D8D;
}
.nav .site-menu .menu-element {
a:not([class|=router-link]) {
color: #8D8D8D;
&:hover {
color: #A2CF00;
}
}
}
}
.container {
display: flex;
@@ -143,55 +225,3 @@
}
}
</style>
<script>
export default {
name: 'SiteHeader',
data () {
return {
menuItems: [
{
slug: 'start',
title: 'Start',
uri: '/'
},
{
slug: 'projects',
title: 'Projekty',
uri: '/projects'
},
{
slug: 'about',
title: 'O mnie',
uri: '/about'
},
{
slug: 'contact',
title: 'Kontakt',
uri: '/contact'
}
],
clicked: false,
publicPath: process.env.VUE_APP_BASE_URL + '/'
}
},
computed: {
clickedStatus () {
return this.clicked
}
},
methods: {
changeClickedStatus () {
this.clicked = !this.clicked
},
clickMenu () {
this.changeClickedStatus()
},
linkClicked () {
if (this.clicked) {
this.changeClickedStatus()
}
}
}
}
</script>

View File

@@ -1,29 +1,88 @@
<template>
<div class="projects">
<slot></slot>
<slot />
<div class="container">
<div class="project"
v-for="project in projects"
:key="project.title.slug">
<img v-if="project.images.small" class="project_image" :src="project.images.small" :alt="project.title" />
<img v-else-if="project.images.large" class="project_image" :src="project.images.large" :alt="project.title" />
<div
v-for="project in projects"
:key="project.title.slug"
class="project"
>
<img
v-if="project.images.small"
class="project_image"
:src="project.images.small"
:alt="project.title"
>
<img
v-else-if="project.images.large"
class="project_image"
:src="project.images.large"
:alt="project.title"
>
<div class="project_content">
<h3 class="project_title">{{ project.title }}</h3>
<div class="project_release">{{ project.version }}</div>
<div class="project_description" v-html="markdownToText(project)"></div>
<h3 class="project_title">
{{ project.title }}
</h3>
<div class="project_release">
{{ project.version }}
</div>
<div
class="project_description"
v-html="markdownToText(project)"
/>
</div>
<div class="more-button">
<base-btn has-icon
icon="eye"
is-reverse
@click.native="$router.push({ name: 'Project', params: { id: project.id } })"
class="btn">O projekcie</base-btn>
<BaseButton
has-icon
icon="eye"
is-reverse
class="btn"
@click="router.push({ name: 'Project', params: { id: project.id } })"
>
O projekcie
</BaseButton>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useStore } from 'vuex'
import BaseButton from './buttons/BaseButton'
import { marked } from 'marked'
defineProps({
projects: {
type: Array,
default: () => []
}
})
const route = useRoute()
const router = useRouter()
const store = useStore()
onMounted(() => {
const header = {
title: route.meta.title,
description: [
'Witam Państwa na podstronie z moimi projektami!'
]
}
store.commit('setHeader', header)
})
function markdownToText (project) {
const projectText = marked.parse(project.description)
const nodeElement = document.createElement('div')
nodeElement.innerHTML = projectText
return nodeElement.querySelector('p').innerText.substr(0, 350)
}
</script>
<style lang="scss">
@import "scss/media";
@@ -178,40 +237,3 @@
}
}
</style>
<script>
import BaseButton from './BaseButton'
import { marked } from 'marked'
export default {
name: 'SelectedProjects',
data () {
return {}
},
mounted () {
const header = {
title: this.$route.meta.title,
description: [
'Witam Państwa na podstronie z moimi projektami!'
]
}
this.$store.commit('setHeader', header)
},
methods: {
markdownToText (project) {
const projectText = marked.parse(project.description)
const nodeElement = document.createElement('div')
nodeElement.innerHTML = projectText
return nodeElement.querySelector('p').innerText.substr(0, 350)
}
},
props: {
projects: {
type: Array
}
},
components: {
'base-btn': BaseButton
}
}
</script>

View File

@@ -1,48 +1,45 @@
<template>
<header :class="thisClass">
<navigation/>
<div v-if="isTitle" class="header-info" :class="{ 'header-info-home': isHome }">
<navigation />
<div
v-if="isTitle"
class="header-info"
:class="{ 'header-info-home': isHome }"
>
<h1>{{ getTitle }}</h1>
<p v-if="isDescription && descriptionType === 'string'">{{ getDescription }}</p>
<p v-else-if="isDescription && descriptionType === 'array'"
v-for="(desc, key) in getDescription" :key="key">{{ desc }}</p>
<p v-if="isDescription && descriptionType === 'string'">
{{ getDescription }}
</p>
<p
v-for="(desc, key) in getDescription"
v-else-if="isDescription && descriptionType === 'array'"
:key="key"
>
{{ desc }}
</p>
</div>
</header>
</template>
<script>
import Navigation from '@/components/Navigation'
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
import { useRoute } from 'vue-router'
import Navigation from './NavigationHeader'
export default {
name: 'SiteHeader',
computed: {
isHome () {
return this.$route.path === '/'
},
thisClass () {
return [this.isHome ? 'home-page' : 'sub-page']
},
getTitle () {
return this.$store.getters.getHeader.title
},
isTitle () {
return this.getTitle !== null
},
getDescription () {
return this.$store.getters.getHeader.description
},
descriptionType () {
const isArray = this.getDescription instanceof Array
return isArray ? 'array' : typeof this.getDescription
},
isDescription () {
return this.getDescription !== null
}
},
components: {
navigation: Navigation
}
}
const store = useStore()
const route = useRoute()
const isHome = computed(() => route.path === '/')
const thisClass = computed(() => [isHome.value ? 'home-page' : 'sub-page'])
const getTitle = computed(() => store.getters.getHeader.title)
const isTitle = computed(() => getTitle.value !== null)
const getDescription = computed(() => store.getters.getHeader.description)
const isDescription = computed(() => getDescription.value !== null)
const descriptionType = computed(() => {
const isArray = getDescription.value instanceof Array
return isArray ? 'array' : typeof getDescription.value
})
</script>
<style lang="scss">

View File

@@ -0,0 +1,45 @@
<template>
<button
class="btn"
:class="{ 'btn-reverse': isReverse }"
title="Wyślij wiadomość"
>
<font-awesome-icon
v-if="hasIcon && icon"
class="icon"
:icon="icon"
/>
<slot />
</button>
</template>
<script setup>
import { defineProps } from 'vue'
defineProps({
hasIcon: {
type: Boolean,
default: false
},
icon: {
type: String,
default: ''
},
isReverse: {
type: Boolean,
default: false
}
})
</script>
<style lang="scss" scoped>
@import "../../../scss/btn";
.btn {
@include button($has-icon: true);
}
.btn-reverse {
@include button(true, true);
}
</style>

View File

@@ -0,0 +1,33 @@
<template>
<button class="btn">
<font-awesome-icon
v-if="hasIcon && icon"
class="icon"
:icon="icon"
/>
<slot />
</button>
</template>
<script setup>
import { defineProps } from 'vue'
defineProps({
hasIcon: {
type: Boolean,
default: false
},
icon: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
@import "../../../scss/btn";
.btn {
@include ghost-button();
}
</style>

View File

@@ -3,23 +3,40 @@
<div class="container">
<div id="grid">
<div id="grid-text">
<h2 class="name">Kamil Niemczycki</h2>
<div class="tagline">Web Developer</div>
<p>Jestem młodym i ambitnym inżynierem oprogramowania. Specjalizuję się w tworzeniu frontendów i backendów.
W projektach wykorzystuję techologie oparte o PHP i JavaScript, tworząc skomplikowane i skalowalne aplikacje internetowe.</p>
<h2 class="name">
Kamil Niemczycki
</h2>
<div class="tagline">
Web Developer
</div>
<p>
Jestem młodym i ambitnym inżynierem oprogramowania. Specjalizuję się w tworzeniu frontendów i backendów.
W projektach wykorzystuję techologie oparte o PHP i JavaScript, tworząc skomplikowane i skalowalne aplikacje internetowe.
</p>
<div class="buttons">
<base-btn has-icon
icon="portrait"
is-reverse
@click.native="scrollTo('.selected-projects')">Wybrane projekty</base-btn>
<base-btn has-icon
icon="user"
@click.native="$router.push('about')">Więcej o mnie</base-btn>
<BaseButton
has-icon
icon="portrait"
is-reverse
@click="scrollTo('.selected-projects')"
>
Wybrane projekty
</BaseButton>
<BaseButton
has-icon
icon="user"
@click="router.push('about')"
>
Więcej o mnie
</BaseButton>
</div>
</div>
<div id="grid-photo">
<figure id="about-photo">
<img :src="`${publicPath}assets/me.jpg`" />
<img
:src="`${publicPath}assets/me.jpg`"
alt="Moje zdjęcie"
>
</figure>
</div>
</div>
@@ -27,6 +44,20 @@
</div>
</template>
<script setup>
import BaseButton from '../buttons/BaseButton'
import { useRouter } from 'vue-router'
const router = useRouter()
const publicPath = process.env.VUE_APP_BASE_URL + '/'
function scrollTo(id) {
document.querySelector(id).scrollIntoView({
behavior: 'smooth'
})
}
</script>
<style lang="scss">
@import "scss/media";
@@ -122,24 +153,3 @@
}
}
</style>
<script>
import BaseButton from '../BaseButton'
export default {
data () {
return {
publicPath: process.env.VUE_APP_BASE_URL + '/'
}
},
methods: {
scrollTo (id) {
document.querySelector(id).scrollIntoView({
behavior: 'smooth'
})
}
},
components: {
'base-btn': BaseButton
}
}
</script>

View File

@@ -1,253 +0,0 @@
<template>
<div class="experiences">
<div class="container">
<h2>Wykorzystywane technologie</h2>
<p>
Programowaniem stron internetowych zajmuję się najmłodszych lat, czyli od 2011 roku. Pierwsze projekty były
proste i najczęściej na użytek własny. Jednakże, doświadczenie zebrane przez lata pomogło mi wyselekcjonować
technologie, które wspomagają pisanie i rozwijanie oprogramowania.
</p>
<div class="skills">
<div v-for="(skill, key) in list" :key="key" class="skill-group">
<header class="skill-header">
<div class="tech-icons">
<font-awesome-icon
v-for="(icon, keyIcon) in skill.icons"
:key="keyIcon" :class="icon.class"
:icon="['fab', icon.name]"
:alt="icon.alt"
:title="icon.title" />
</div>
<h3>{{ skill.header }}</h3>
</header>
<p v-if="skill.showMore || skill.description.length < 200">{{ skill.description }}</p>
<p v-else>{{ parseText(skill.description) }}... <a @click="changeMoreStatus(skill)">Więcej</a></p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Experiences',
data () {
return {
list: []
}
},
mounted () {
this.list = [
{
icons: [
{
class: 'icon-php',
name: 'php',
alt: 'Ikona przedstawiająca język PHP',
title: 'Ikona PHP'
},
{
class: 'icon-laravel',
name: 'laravel',
alt: 'Ikona przedstawiająca framework Laravel',
title: 'Ikona Laravel'
}
],
header: 'PHP & Laravel',
showMore: false,
description: `PHP wraz z frameworkiem Laravel wykorzystuję do tworzenia nowych projektów. Laravel znacznie
przyspiesza pisanie części backendowej, odciążając programistę od pisania skomplikownych filtrów i monotonnych
walidacji przyjmowanych danych od użytkowników. Jednakże, choć dobrze pracuje mi się w środowisku Laravel
w miarę możliwości staram się pisać kod w samym PHP z wykorzystaniem bibliotek i paczek autorskich.`
},
{
icons: [
{
class: 'icon-js',
name: 'js-square',
alt: 'Ikona przedstawiająca język JavaScript',
title: 'Ikona JavaScript'
}
],
header: 'JavaScript Vanilla',
showMore: false,
description: `JavaScript wykorzystuję głównie do logiki frontendowej jak i pisania kodu wykonywalnego po stronie
przeglądarki internetowej. Wykorzystując możliwości reaktywnych frameworków mogę przyspieszyć pisanie UI.`
},
{
icons: [
{
class: 'icon-vue',
name: 'vuejs',
alt: 'Ikona przedstawiająca framework Vue.js',
title: 'Ikona Vue.js'
}
],
header: 'Vue',
showMore: false,
description: `Framework ten wspomaga mnie w pisaniu aplikacji frontendowej. Przyspiesza tworzenie warstwy
wizualnej, obsługę interakcji z użytkownikiem i bindowanie zdefiniowanych pól, które są odpowiednio renderowane
przez przeglądarkę. To wszystko, umożliwia łatwe łączenie CSS i HTML z logiką i otrzymanymi danymi z API.`
},
{
icons: [
{
class: 'icon-html5',
name: 'html5',
alt: 'Ikona przedstawiająca HTML5',
title: 'Ikona HTML5'
},
{
class: 'icon-css3',
name: 'css3-alt',
alt: 'Ikona przedstawiająca CSS3',
title: 'Ikona CSS3'
}
],
header: 'HTML & CSS',
showMore: false,
description: `HTML i CSS to już nieodłączne technologie, które w łatwy sposób umożliwiają nadanie wizualnej
spójności dla treści, obrazów lub ułożenia elementów. Nie raz, odpowednie zaprojektowanie interfejsu użytkownika
przyspiesza integrację z naszą stroną/sklepem. Ma to przełożenie na zyski dla obu stron - firmy (finanse)
i klienta (czas).`
},
{
icons: [
{
class: 'icon-node',
name: 'node',
alt: 'Ikona przedstawiająca Node.js',
title: 'Ikona Node.js'
}
],
header: 'Node.js',
showMore: false,
description: `Node jest wieloplatformowym środowiskiem uruchomieniowym, umożliwiającym uruchomienie kodu
JavaScript po stronie backendowej. Jest to bardzo przyjemny język, który ułatwia projektowanie logiki biznesowej
aplikacji, dla mniej wymagających serwisów pod względem analizy i przetwarzania danych.`
},
{
icons: [
{
class: 'icon-sass',
name: 'sass',
alt: 'Ikona przedstawiająca Sass',
title: 'Ikona Sass'
}
],
header: 'Sass',
showMore: false,
description: `Sass zwiększa czytelność kodu dla programisty. Zawiera abstrakcje znane z języków obiektowych,
przy czym ogranicza powtarzalność i umożliwia optymalizację kodu. Kompilacja napisanego stylu w Sass jest
przekształcana do formy czytelnej dla silnika graficznego przeglądarki, co przyspiesza proces generowania strony.`
}
]
},
methods: {
parseText (text) {
let shortText = text.substr(0, 200)
if (shortText.slice(-1) === ' ') {
shortText = text.substr(0, 199)
}
return shortText
},
changeMoreStatus (skill) {
skill.showMore = !skill.showMore
}
}
}
</script>
<style lang="scss">
@import "scss/media";
.experiences {
.container {
padding-top: 45px;
padding-bottom: 45px;
.skills {
display: grid;
grid-template-columns: repeat(4, 1fr);
column-gap: 20px;
row-gap: 20px;
margin-top: 15px;
.skill-group {
background-color: #fafafa;
padding: 15px;
border-radius: 5px;
.tech-icons {
font-size: 1.8em;
margin-bottom: 3px;
& > svg[class*='icon'] {
margin-right: 10px;
&:last-child {
margin-right: 0;
}
}
}
h3 {
font-size: 1.2em;
line-height: 1.6em;
font-weight: bold;
}
.skill-list {
margin-block: auto;
padding-inline: inherit;
margin-left: 5px;
list-style-type: initial;
}
p {
font-size: .9em;
a {
color: #8D8D8D;
cursor: pointer;
}
}
&:hover {
background-color: rgba(0, 0, 0, .03);
}
}
@include media-small {
grid-template-columns: repeat(3, 1fr);
}
@include media-tablet {
grid-template-columns: repeat(2, 1fr);
}
@include media-mobile {
grid-template-columns: repeat(1, 1fr);
}
}
}
}
.icon-js {
color: #F1DE4F;
}
.icon-php {
color: #777bb3;
}
.icon-laravel {
color: #FF2D20;
}
.icon-vue {
color: #64B687;
}
.icon-html5 {
color: #DE6E3C;
}
.icon-node {
color: #64B687;
}
.icon-css3 {
color: #53A7DC;
}
.icon-sass {
color: #BF6B97;
}
</style>

View File

@@ -0,0 +1,255 @@
<template>
<div class="experiences">
<div class="container">
<h2>Wykorzystywane technologie</h2>
<p>
Programowaniem stron internetowych zajmuję się najmłodszych lat, czyli od 2011 roku. Pierwsze projekty były
proste i najczęściej na użytek własny. Jednakże, doświadczenie zebrane przez lata pomogło mi wyselekcjonować
technologie, które wspomagają pisanie i rozwijanie oprogramowania.
</p>
<div class="skills">
<div
v-for="(skill, skillKey) in list"
:key="skillKey"
class="skill-group"
>
<header class="skill-header">
<div class="tech-icons">
<font-awesome-icon
v-for="(icon, iconKey) in skill.icons"
:key="iconKey"
:class="icon.class"
:icon="['fab', icon.name]"
:alt="icon.alt"
:title="icon.title"
/>
</div>
<h3>{{ skill.header }}</h3>
</header>
<p v-if="skill.showMore.value || skill.description.length < 200">
{{ skill.description }}
</p>
<p v-else>
{{ parseText(skill.description) }}... <a @click="changeMoreStatus(skill)">Więcej</a>
</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
let list = [
{
icons: [
{
class: 'icon-php',
name: 'php',
alt: 'Ikona przedstawiająca język PHP',
title: 'Ikona PHP'
},
{
class: 'icon-laravel',
name: 'laravel',
alt: 'Ikona przedstawiająca framework Laravel',
title: 'Ikona Laravel'
}
],
header: 'PHP & Laravel',
showMore: ref(false),
description: `PHP wraz z frameworkiem Laravel wykorzystuję do tworzenia nowych projektów. Laravel znacznie
przyspiesza pisanie części backendowej, odciążając programistę od pisania skomplikownych filtrów i monotonnych
walidacji przyjmowanych danych od użytkowników. Jednakże, choć dobrze pracuje mi się w środowisku Laravel
w miarę możliwości staram się pisać kod w samym PHP z wykorzystaniem bibliotek i paczek autorskich.`
},
{
icons: [
{
class: 'icon-js',
name: 'js-square',
alt: 'Ikona przedstawiająca język JavaScript',
title: 'Ikona JavaScript'
}
],
header: 'JavaScript Vanilla',
showMore: ref(false),
description: `JavaScript wykorzystuję głównie do logiki frontendowej jak i pisania kodu wykonywalnego po stronie
przeglądarki internetowej. Wykorzystując możliwości reaktywnych frameworków mogę przyspieszyć pisanie UI.`
},
{
icons: [
{
class: 'icon-vue',
name: 'vuejs',
alt: 'Ikona przedstawiająca framework Vue.js',
title: 'Ikona Vue.js'
}
],
header: 'Vue',
showMore: ref(false),
description: `Framework ten wspomaga mnie w pisaniu aplikacji frontendowej. Przyspiesza tworzenie warstwy
wizualnej, obsługę interakcji z użytkownikiem i bindowanie zdefiniowanych pól, które są odpowiednio renderowane
przez przeglądarkę. To wszystko, umożliwia łatwe łączenie CSS i HTML z logiką i otrzymanymi danymi z API.`
},
{
icons: [
{
class: 'icon-html5',
name: 'html5',
alt: 'Ikona przedstawiająca HTML5',
title: 'Ikona HTML5'
},
{
class: 'icon-css3',
name: 'css3-alt',
alt: 'Ikona przedstawiająca CSS3',
title: 'Ikona CSS3'
}
],
header: 'HTML & CSS',
showMore: ref(false),
description: `HTML i CSS to już nieodłączne technologie, które w łatwy sposób umożliwiają nadanie wizualnej
spójności dla treści, obrazów lub ułożenia elementów. Nie raz, odpowednie zaprojektowanie interfejsu użytkownika
przyspiesza integrację z naszą stroną/sklepem. Ma to przełożenie na zyski dla obu stron - firmy (finanse)
i klienta (czas).`
},
{
icons: [
{
class: 'icon-node',
name: 'node',
alt: 'Ikona przedstawiająca Node.js',
title: 'Ikona Node.js'
}
],
header: 'Node.js',
showMore: ref(false),
description: `Node jest wieloplatformowym środowiskiem uruchomieniowym, umożliwiającym uruchomienie kodu
JavaScript po stronie backendowej. Jest to bardzo przyjemny język, który ułatwia projektowanie logiki biznesowej
aplikacji, dla mniej wymagających serwisów pod względem analizy i przetwarzania danych.`
},
{
icons: [
{
class: 'icon-sass',
name: 'sass',
alt: 'Ikona przedstawiająca Sass',
title: 'Ikona Sass'
}
],
header: 'Sass',
showMore: ref(false),
description: `Sass zwiększa czytelność kodu dla programisty. Zawiera abstrakcje znane z języków obiektowych,
przy czym ogranicza powtarzalność i umożliwia optymalizację kodu. Kompilacja napisanego stylu w Sass jest
przekształcana do formy czytelnej dla silnika graficznego przeglądarki, co przyspiesza proces generowania strony.`
}
]
function parseText(text) {
let shortText = text.substr(0, 200)
if (shortText.slice(-1) === ' ') {
shortText = text.substr(0, 199)
}
return shortText
}
function changeMoreStatus(skill) {
skill.showMore.value = !skill.showMore.value
}
</script>
<style lang="scss">
@import "scss/media";
.experiences {
.container {
padding-top: 45px;
padding-bottom: 45px;
.skills {
display: grid;
grid-template-columns: repeat(4, 1fr);
column-gap: 20px;
row-gap: 20px;
margin-top: 15px;
.skill-group {
background-color: #fafafa;
padding: 15px;
border-radius: 5px;
.tech-icons {
font-size: 1.8em;
margin-bottom: 3px;
& > svg[class*='icon'] {
margin-right: 10px;
&:last-child {
margin-right: 0;
}
}
}
h3 {
font-size: 1.2em;
line-height: 1.6em;
font-weight: bold;
}
.skill-list {
margin-block: auto;
padding-inline: inherit;
margin-left: 5px;
list-style-type: initial;
}
p {
font-size: .9em;
a {
color: #8D8D8D;
cursor: pointer;
}
}
&:hover {
background-color: rgba(0, 0, 0, .03);
}
}
@include media-small {
grid-template-columns: repeat(3, 1fr);
}
@include media-tablet {
grid-template-columns: repeat(2, 1fr);
}
@include media-mobile {
grid-template-columns: repeat(1, 1fr);
}
}
}
}
.icon-js {
color: #F1DE4F;
}
.icon-php {
color: #777bb3;
}
.icon-laravel {
color: #FF2D20;
}
.icon-vue {
color: #64B687;
}
.icon-html5 {
color: #DE6E3C;
}
.icon-node {
color: #64B687;
}
.icon-css3 {
color: #53A7DC;
}
.icon-sass {
color: #BF6B97;
}
</style>

View File

@@ -7,41 +7,33 @@
</div>
</projects>
<div class="more-button">
<GhostButton @click.native="$router.push('projects')">
<GhostButton @click="router.push('projects')">
ZOBACZ WIĘCEJ
</GhostButton>
</div>
</section>
</template>
<script>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import Projects from '../SelectedProjects'
import GhostButton from '../GhostButton'
import GhostButton from '../buttons/GhostButton'
export default {
name: 'FavoriteProjects',
data () {
return {
publicPath: process.env.VUE_APP_BASE_URL + '/',
select_projects: []
}
},
mounted () {
this.loadProjectList()
},
methods: {
loadProjectList () {
fetch(process.env.VUE_APP_API_URL + '/projects?category=selected')
.then(response => response.json())
.then(data => {
this.select_projects = data
})
}
},
components: {
GhostButton,
Projects
}
const router = useRouter()
let select_projects = ref([])
onMounted(() => {
loadProjectList()
})
function loadProjectList() {
fetch(process.env.VUE_APP_API_URL + '/projects?category=selected')
.then(response => response.json())
.then(data => {
select_projects.value = data
})
}
</script>

View File

@@ -1,22 +1,106 @@
<template>
<div class="contact_container">
<div class="message"></div>
<header class="container_head">Formularz kontaktowy:</header>
<form id="form-point" @submit="formSubmit">
<input v-model="emailValue" class="contact_input" type="text" name="email" placeholder="Twój adres e-mail." />
<textarea v-model="messageValue" class="contact_input" name="message" placeholder="Twoja wiadomość."></textarea>
<input v-model="senderValue" class="contact_input" type="text" name="sender" placeholder="Twój podpis." />
<base-btn is-reverse :disabled="buttonDisabled">Wyślij</base-btn>
<div
v-if="hasMessageError"
class="message message_error"
>
{{ messageError }}
</div>
<div
v-if="hasMessageOkStatus"
class="message message_ok"
>
{{ messageOk }}
</div>
<header class="container_head">
Formularz kontaktowy:
</header>
<form
id="form-point"
@submit="formSubmit"
>
<label
class="label-info"
:class="{ 'label-error': isEmailError }"
for="email"
>
E-mail:
</label>
<input
id="email"
v-model="emailValue"
class="contact_input"
:class="{ 'contact_input-error': isEmailError }"
type="text"
name="email"
placeholder="przemek.kowalski@gmail.com"
>
<span
v-if="isEmailError"
class="error-message"
>
E-mail musi być poprawny, np. przemek.kowalski@gmail.com
</span>
<label
class="label-info"
:class="{ 'label-error': isMessageError }"
for="message"
>
Wiadomość:
</label>
<textarea
id="message"
v-model="messageValue"
class="contact_input"
:class="{ 'contact_input-error': isMessageError }"
name="message"
placeholder="Chciałbym zlecić wykonanie strony..."
/>
<span
v-if="isMessageError"
class="error-message"
>
Wiadomość musi zawierać przynajmniej 3 znaki!
</span>
<label
class="label-info"
:class="{ 'label-error': isSenderError }"
for="sender"
>
Podpis nadawcy:
</label>
<input
id="sender"
v-model="senderValue"
class="contact_input"
:class="{ 'contact_input-error': isSenderError }"
type="text"
name="sender"
placeholder="np. Przemek Kowalski"
>
<span
v-if="isSenderError"
class="error-message"
>
Podpis musi zawierać przynajmniej 3 znaki!
</span>
<BaseButton
is-reverse
:disabled="isButtonDisabled"
>
Wyślij
</BaseButton>
</form>
</div>
</template>
<script>
import BaseButton from '../../BaseButton'
<script setup>
import BaseButton from '../../buttons/BaseButton'
import { ref, reactive, watch, computed } from 'vue'
function emailValidate (mailObj) {
const mailFormat = /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/
return !!mailObj.value.match(mailFormat)
return mailObj.match(mailFormat)
}
async function postData (url = '', data = {}) {
@@ -31,99 +115,89 @@ async function postData (url = '', data = {}) {
return response.json()
}
export default {
name: 'MailContact',
data () {
return {
buttonDisabled: false,
statusError: 0,
email: {},
emailValue: '',
message: {},
messageValue: '',
sender: {},
senderValue: ''
}
},
components: {
'base-btn': BaseButton
},
watch: {
emailValue (value) {
if (this.statusError > 0 && emailValidate(this.email)) {
this.email.classList.remove('contact_input-error')
}
},
messageValue (value) {
if (this.statusError > 0 && value !== '') {
this.message.classList.remove('contact_input-error')
}
},
senderValue (value) {
if (this.statusError > 0 && value !== '') {
this.sender.classList.remove('contact_input-error')
}
}
},
methods: {
clearErrors () {
this.statusError = 0
this.email.classList.remove('contact_input-error')
this.message.classList.remove('contact_input-error')
this.sender.classList.remove('contact_input-error')
},
checkForm () {
if (!emailValidate(this.email)) {
this.email.classList.add('contact_input-error')
this.statusError++
}
if (this.message.value === '') {
this.message.classList.add('contact_input-error')
this.statusError++
}
if (this.sender.value === '') {
this.sender.classList.add('contact_input-error')
this.statusError++
}
},
formSubmit (event) {
event.preventDefault()
const buttonDisabled = ref(false)
const statusError = ref(0)
const emailValue = ref('')
const messageValue = ref('')
const senderValue = ref('')
this.email = event.target[0]
this.message = event.target[1]
this.sender = event.target[2]
const messageError = ref('')
const messageOk = ref('')
this.clearErrors()
this.checkForm()
const errors = reactive({
email: false,
message: false,
sender: false
})
const messageElement = document.querySelector('.message')
messageElement.classList.remove('message_ok', 'message_error')
const isButtonDisabled = computed(() => buttonDisabled.value || errors.email || errors.message || errors.sender)
const isEmailError = computed(() => errors.email)
const isMessageError = computed(() => errors.message)
const isSenderError = computed(() => errors.sender)
const hasMessageError = computed(() => messageError.value)
const hasMessageOkStatus = computed(() => messageOk.value && !hasMessageError.value)
if (this.statusError === 0) {
this.buttonDisabled = true
postData('/send', {
email: this.emailValue,
message: this.messageValue,
sender: this.senderValue
}).then(result => {
messageElement.classList.add(
result.error ? 'message_error' : 'message_ok'
)
watch([statusError, emailValue], ([errorCount, value]) => {
errors.email = errorCount > 0 && !emailValidate(value)
})
messageElement.textContent = result.message
if (!result.error) {
this.messageValue = ''
this.emailValue = ''
this.senderValue = ''
}
this.buttonDisabled = false
}).catch(() => {
messageElement.classList.add('message_error')
messageElement.textContent = 'Wystąpił błąd podczas wysyłania wiadomości. Proszę spróbować później.'
this.buttonDisabled = false
})
watch([statusError, messageValue], ([errorCount, value]) => {
errors.message = errorCount > 0 && (value === '' || value.length < 3)
})
watch([statusError, senderValue], ([errorCount, value]) => {
errors.sender = errorCount > 0 && (value === '' || value.length < 3)
})
function clearErrors() {
statusError.value = 0
errors.email = false
errors.message = false
errors.sender = false
}
function checkForm() {
clearErrors()
if (!emailValidate(emailValue.value)) {
errors.email = true
statusError.value++
}
if (messageValue.value === '') {
errors.message = true
statusError.value++
}
if (senderValue.value === '') {
errors.sender = true
statusError.value++
}
}
function formSubmit(event) {
event.preventDefault()
checkForm()
if (statusError.value === 0) {
console.log('Send!')
buttonDisabled.value = true
postData('https://kamilcraft.com/send', {
email: emailValue.value,
message: messageValue.value,
sender: senderValue.value
}).then(result => {
if (result.error) {
messageError.value = result.message
} else {
messageOk.value = result.message
messageValue.value = ''
emailValue.value = ''
senderValue.value = ''
}
}
buttonDisabled.value = false
}).catch(() => {
messageError.value = 'Wystąpił błąd podczas wysyłania wiadomości. Proszę spróbować później.'
buttonDisabled.value = false
})
}
}
</script>
@@ -153,17 +227,35 @@ export default {
font-weight: bold;
}
.contact_container input, .contact_container textarea {
width: 97%;
max-width: 97%;
border: 0;
border-bottom: 2px solid #c9c9c9;
padding: 10px 10px 8px;
font-size: 1em;
font-family: var(--font-family);
line-height: 1.3em;
margin-bottom: 10px;
border-radius: 5px;
.contact_container {
.label-info {
width: 97%;
padding-bottom: 5px;
color: #7a7a7a;
}
.error-message {
width: 97%;
padding: 5px 0 10px;
color: #d44950;
}
input, textarea {
width: 97%;
max-width: 97%;
border: 0;
border-bottom: 2px solid #c9c9c9;
padding: 10px 10px 8px;
font-size: 1em;
font-family: var(--font-family);
line-height: 1.3em;
margin-bottom: 15px;
border-radius: 5px;
}
}
.contact_input::placeholder {
color: #bdbdbd;
}
.contact_input:focus, .contact_input:focus {
@@ -177,14 +269,14 @@ textarea.contact_input {
min-height: 150px;
}
.contact_input-error, .contact_input-error {
background-color: #ffc3b0;
border-color: #ff865f;
color: #c90000;
input.contact_input-error, textarea.contact_input-error {
border-color: #d44950;
color: #d44950;
margin-bottom: 0;
}
.contact_input-error::placeholder, .contact_input-error::placeholder {
color: #c9000094;
color: #d7626a;
}
input[disabled].contact_input {
@@ -211,8 +303,8 @@ input[disabled].contact_input {
}
.message_error {
background-color: #ffc3b0;
border: 1px solid #ff865f;
color: #c90000;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
</style>

View File

@@ -1,53 +1,86 @@
<template>
<div class="contact_info">
<header class="info_head">Inne formy kontaktu:</header>
<header class="info_head">
Inne formy kontaktu:
</header>
<div class="contact_element">
<img class="contact_element_icon"
src="/assets/img/instagram.jpg" />
<span id="instagram" class="contact_element_text">
<a href="https://www.instagram.com/nikcamii/"
target="_blank"
rel="noopener nofollow noreferrer">Instagram: @NiKCamii</a>
<img
class="contact_element_icon"
src="/assets/img/instagram.jpg"
alt="Instagram"
>
<span
id="instagram"
class="contact_element_text"
>
<a
href="https://www.instagram.com/nikcamii/"
target="_blank"
rel="noopener nofollow noreferrer"
>Instagram: @NiKCamii</a>
</span>
</div>
<div class="contact_element">
<img class="contact_element_icon" src="/assets/img/facebook.jpg" />
<span id="facebook" class="contact_element_text">
<a href="https://www.facebook.com/nikcamii/"
target="_blank"
rel="noopener nofollow noreferrer">Facebook: @NiKCamii</a>
<img
class="contact_element_icon"
src="/assets/img/facebook.jpg"
alt="Facebook"
>
<span
id="facebook"
class="contact_element_text"
>
<a
href="https://www.facebook.com/nikcamii/"
target="_blank"
rel="noopener nofollow noreferrer"
>Facebook: @NiKCamii</a>
</span>
</div>
<div class="contact_element">
<img class="contact_element_icon"
src="/assets/img/twitter.jpg" />
<span id="twitter" class="contact_element_text">
<a href="https://twitter.com/nikcamii"
target="_blank"
rel="noopener nofollow noreferrer">Twitter: @NiKCamii</a>
<img
class="contact_element_icon"
src="/assets/img/twitter.jpg"
alt="Twitter"
>
<span
id="twitter"
class="contact_element_text"
>
<a
href="https://twitter.com/nikcamii"
target="_blank"
rel="noopener nofollow noreferrer"
>Twitter: @NiKCamii</a>
</span>
</div>
<div class="contact_element">
<img class="contact_element_icon"
src="/assets/img/gg.png" />
<span id="gg" class="contact_element_text">GG: 38429969</span>
<img
class="contact_element_icon"
src="/assets/img/gg.png"
alt="Gadu-Gadu"
>
<span
id="gg"
class="contact_element_text"
>GG: 38429969</span>
</div>
<div class="contact_element">
<img class="contact_element_icon"
src="/assets/img/user.jpg" />
<span id="email" class="contact_element_text">
<img
class="contact_element_icon"
src="/assets/img/user.jpg"
alt="E-mail"
>
<span
id="mailto"
class="contact_element_text"
>
<a href="mailto:contact@kamilcraft.com">Email: contact@kamilcraft.com</a>
</span>
</div>
</div>
</template>
<script>
export default {
name: 'OtherContact'
}
</script>
<style lang="scss" scoped>
@import "scss/media";
@@ -113,7 +146,7 @@ export default {
color: #5eaade;
}
#email {
#mailto {
color: #696969;
}
@@ -122,7 +155,7 @@ export default {
}
@include media-tablet {
#instagram, #facebook, #twitter, #email, #gg {
#instagram, #facebook, #twitter, #mailto, #gg {
font-size: 1em;
line-height: 1.2em;
}