Vue.js Landing page & Admin-panel  Visit site

  • Vue.js
  • PUG
  • SCSS
  • JavaScript
  • SVG
  • Fake backend
  • JWT Auth

Web developer's personal portfolio site and admin panel developed with the Vue.js framework. The project used a fake backend to be able to work without a server (without a backend). Go to the admin panel and see how everything works.

Vue.js Landing and Admin-panel

Web developer's personal portfolio site and admin panel developed with the Vue.js framework. The project used a fake backend to be able to work without a server (without a backend).

All data is stored in Vuex storage. Data.js has the initial state of the data: 2 users, 5 works, 5 reviews, and 3 skills.

In the admin panel, you can perform the operations of creating, editing and deleting positions, as well as resetting the states to the initial values.

Home page

The home page contains the following sections:

  1. Hero - first screen
  2. About me with skills
  3. Works: cards with hover effects.
  4. Reviews: section horizontal scrolling and Add Review button.
  5. Contacts: links to the developer's pages and a feedback form in the modal window.

Implemented on the page:

  • Page loading animation.
  • Animation of elements when scrolling: appearance, disappearance and parallax. All animations are designed with JavaScript and CSS.
  • Text and SVG animations.
  • SVG sprite.
  • Horizontal scrolling of the reviews section.
  • Modal windows.
  • Adding user reviews.
  • Feedback form.
  • Adaptability.
  • Dark theme in smartphones.

Admin panel

Admin panel code on GitHub: vuejs-landing-and-admin

For the project to work without a server API (without a backend), a fake backend is implemented. This functionality modifies the fetch() function to intercept certain API requests and mimic the behavior of the real API. And any uncaught requests are passed to the real fetch() function.

Implemented in the admin panel:

  1. Login page using JWT (Json Web Token) authentication.
  2. CRUD operations with works, skills and reviews.
  3. Form field validation with simple-vue-validator.
  4. Demo image upload for works and reviews.
  5. Notifications for the user in the form of modal pop-ups at the bottom.
  6. Adaptability.

Used tools and packages:

  1. Template engine Pug.
  2. Sass preprocessor.
  3. Packages used for Svg sprite: svg-sprite-loader, svgo and svgo-loader.
  4. Vuex - for working with data (state management).
  • Home desktop
  • Modal desktop
  • Home mobile
  • Admin
Landing Page Portfolio site
Popup modal window Vue.js
Vue mobile layout
Vue.js login page Vue.js admin panel Vue.js admin panel: reviews

Admin panel code on GitHubvue-landing-and-admin

Here below is just the script file:

Login.vue
										<template lang="pug">
  .modal
    .modal__body
      h3.modal__headline Login
      p
        b Username
        | : admin
        br
        b Password
        | : qqwwaass

      form#login_form.login-form(@submit.prevent="handleSubmit")

        .form-group(:class='{"focused": checkValueUsername, "error": validation.firstError("user.username")}')
          label.form-label(for='username') Username
          input#username.form-control(
            type='text'
            name='username'
            v-model="user.username"
            @focus="moveLabel('focus', $event)"
            @blur="moveLabel('blur', $event)")
          .form-group__error
            error-tooltip( :errorText="validation.firstError('user.username')" )

        .form-group(:class='{"focused": checkValuePass, "error": validation.firstError("user.password")}')
          label.form-label(for='password') Password
          input#password.form-control.password(
            type='password'
            name='password'
            v-model="user.password"
            @focus="moveLabel('focus', $event)"
            @blur="moveLabel('blur', $event)")
          .form-group__error
            error-tooltip( :errorText="validation.firstError('user.password')" )

        .form-group.form-footer
          button.btn.primary-btn(type='submit' :disabled="disableSubmit") Sign in
          router-link(to="/") Back to site

    .modal__overlay
</template>

<script>
import { Validator } from 'simple-vue-validator';
import { mapActions, mapGetters, mapMutations } from "vuex";

export default {
  mixins: [require('simple-vue-validator').mixin],

  data() {
    return {
      disableSubmit: false,
      user: {
        username: "",
        password: ""
      },
      checkValueUsername: false,
      checkValuePass: false
    }
  },

  components: {
    ErrorTooltip: () => import('@/components/ErrorTooltip.vue')
  },

  computed: {
    ...mapGetters("users", ["loggedUser"])
  },

  created () {
    this.logout();
  },

  validators: {
    'user.username': (value) => {
      return Validator.value(value).required('Enter Username')
    },

    'user.password': (value) => {
      return Validator.value(value).required('Enter Password')
    }
  },

  methods: {
    ...mapActions("users", ["login", "setLoggedUser", "logout"]),
    ...mapMutations("tooltip", ["SHOW_TOOLTIP"]),

    moveLabel: function (typeEvent, event) {
      let target = event.target

      if (typeEvent == 'focus' && !target.value.trim().length) { // zero-length string AFTER a trim
        target.id == 'username' ? this.checkValueUsername = true : this.checkValuePass = true
      }
      if (typeEvent == 'blur' && !target.value.trim().length) {
        target.id == 'username' ? this.checkValueUsername = false : this.checkValuePass = false
      }
    },

    async handleSubmit() {

      if ((await this.$validate()) === false) return;
      this.disableSubmit = true;

      try {
        const loggedUser = await this.login(this.user);

        if (loggedUser) {

          this["SHOW_TOOLTIP"]({
              type: "success",
              text: "Welcome to Admin panel!"
            });
        } else {

          this["SHOW_TOOLTIP"]({
            type: "error",
            text: "The email address or password is incorrect!"
          });
        }

        this.validation.reset();

      } catch (error) {
        this["SHOW_TOOLTIP"]({
          type: "error",
          text: "Backend not work!"
        });
      } finally {
        this.disableSubmit = false;
      }
    },
  }
}
</script>

<style lang="scss">
@import "../styles/admin.scss";

.modal {
  position: fixed;
  z-index: 9997;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100vh;

  .modal__body {
    position: relative;
    z-index: 9999;
    width: 60%;
    max-width: 450px;
    min-width: 280px;
    height: auto;
    margin: 0;
    padding: 30px 5%;
    border-radius: $border-radius;
    background-color: #fff;

    @media (max-width: 567px) {
      width: 86%;
    }

    .modal__headline {
      text-align: center;
      font-size: 4rem;
      font-weight: 400;
      margin-bottom: 30px;
    }

    p {
      font-size: 1.2rem;
    }
  }

  .modal__overlay {
    position: fixed;
    z-index: 9998;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100vh;
    background-color: rgba(40,52,65,1);
  }
}

.form-group {
  position: relative;
  height: 45px;
  margin: 0 0 20px;
  text-align: center;

  .form-label {
    position: absolute;
    display: inline-block;
    left: 0; top: 0;
    width: auto;
    height: 15px;
    transform: translate(0px, 1px);
    transform-origin: 0 0;
    white-space: nowrap;
    will-change: transform;
    font-size: 13px;
    color: #666;
    padding: 0;
    background-color: #fff;
    cursor: text;
    z-index: 10;
    transition: transform .5s;
  }

  .form-control {
    position: absolute;
    left: 0; top: 0; bottom: 3px; right: 0;
    width: 100%;
    padding: 15px 0;
    letter-spacing: 0.05rem;
    background-color: #fff;
    outline: none;
    z-index: 1;

    &.password {
      letter-spacing: 0.2rem;
    }
  }

  .btn {
    margin: 0 auto;

    & + a {
      margin-left: 20px;
    }
  }

  &.focused {
    .form-label {
      transform: translate(0px, -15px);
    }
  }
}
</style>
									
Back to top