Skip to content
This repository has been archived by the owner on Mar 11, 2020. It is now read-only.

Authorization #20

Merged
merged 18 commits into from
Mar 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
VUE_APP_BASE_API_URL=http://localhost:8000/api
# VUE_APP_BASE_API_URL=http://localhost:8000/api
Gilbord marked this conversation as resolved.
Show resolved Hide resolved
VUE_APP_BASE_API_URL=http://cb7435b1.ngrok.io/api
VUE_APP_CLIENT_ID=KYweldhNZIL8p7vIpM9xJZRXj2LUaUl3XjTaKSfu
VUE_APP_CLIENT_SECRET=9bikWzjLA7V3btoTz0wbuSRxg1e34hPYSA0k7KxhH5MHEOFW3c0Ul6IgSeAQZfa1UYmWx7W8iAqrbO8ZCHwzXn5JDIrdevh211qA8mHm4c845HGITTU6S4rYxH4QJJd8
1 change: 1 addition & 0 deletions .storybook/addons.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import '@storybook/addon-knobs/register';
import '@storybook/addon-actions/register';
2 changes: 2 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons">
546 changes: 284 additions & 262 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"dependencies": {
"axios": "^0.18.0",
"vue": "^2.6.6",
"vue": "^2.6.8",
"vue-class-component": "^6.0.0",
"vue-property-decorator": "^7.0.0",
"vue-router": "^3.0.1",
Expand All @@ -22,8 +22,9 @@
"devDependencies": {
"@babel/core": "^7.3.3",
"@mdi/font": "^3.4.93",
"@storybook/addon-knobs": "^5.0.0-rc.7",
"@storybook/vue": "^5.0.0-rc.7",
"@storybook/addon-actions": "^5.0.1",
"@storybook/addon-knobs": "^5.0.0",
"@storybook/vue": "^5.0.0",
"@types/storybook__vue": "^3.3.2",
"@vue/cli-plugin-babel": "^3.3.0",
"@vue/cli-plugin-typescript": "^3.3.0",
Expand Down
6 changes: 2 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
<v-spacer></v-spacer>
<v-btn
flat
href="https://github.com/vuetifyjs/vuetify/releases/latest"
target="_blank"
:to="{path: '/login'}"
>
<span class="mr-2">Latest Release</span>
<v-icon>open_in_new</v-icon>
<span class="mr-2">Login</span>
</v-btn>
</v-toolbar>

Expand Down
18 changes: 17 additions & 1 deletion src/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
import axios from 'axios';
import { AuthResponseInterface } from '@/interfaces';

const client = axios.create({
baseURL: process.env.VUE_APP_BASE_API_URL,
timeout: 1000,
});

function getPublications() {
export function getPublications() {
return client.get('/publications');
}

export async function authorizeUser(username: string, password: string): Promise<AuthResponseInterface> {
const authData = new FormData();
authData.append('username', username);
authData.append('password', password);
authData.append('client_id', process.env.VUE_APP_CLIENT_ID);
authData.append('client_secret', process.env.VUE_APP_CLIENT_SECRET);
authData.append('grant_type', 'password');

const result = await client.post(
'/oauth/token/',
authData,
);
return result.data as AuthResponseInterface;
}
77 changes: 77 additions & 0 deletions src/components/LoginForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<docs>
Use between two v-spacer for looking good.

Receive current state props: `error` and `loading`.

Emit `change` event when button was clicked.
</docs>

<template>
<v-card
class="pa-4 login-form text-xs-center"
>
<v-form
@submit.prevent="submitLoginForm"
>
<v-text-field
v-model="username"
label="Username"
required
></v-text-field>

<v-text-field
v-model="password"
label="Password"
type="password"
required
></v-text-field>

<v-btn
type="submit"
:loading="loading"
:disabled="loading"
>Login</v-btn>

<v-alert
type="error"
:value="error"
:outline="true"
>
{{ error }}
</v-alert>
</v-form>
</v-card>
</template>

<script lang="ts">
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';


@Component
export default class LoginForm extends Vue {
@Prop({
type: Boolean,
default: false,
})
public loading!: boolean;
@Prop({
type: String,
default: null,
})
public error?: string | null;

public username: string = '';
public password: string = '';

public submitLoginForm() {
this.$emit('change', this.username, this.password);
}
}
</script>

<style>
.login-form {
max-width: 600px;
}
</style>
68 changes: 34 additions & 34 deletions src/components/PublicationItemPlatformIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,48 @@
</template>

<script lang="ts">
import Vue from 'vue';
import {Prop, Component} from 'vue-property-decorator';
import twitterAvatar from '../assets/social_logos/twitter.svg';
import facebookAvatar from '../assets/social_logos/facebook.svg';
import vkAvatar from '../assets/social_logos/vk.svg';
import instagramAvatar from '../assets/social_logos/instagram.svg';
import telegramAvatar from '../assets/social_logos/telegram.svg';
import unknownNetwork from '../assets/social_logos/unknown-network.svg';
import unknownAvatar from '../assets/social_logos/unknown-avatar.svg';
import Vue from 'vue';
import { Prop, Component } from 'vue-property-decorator';
import twitterAvatar from '../assets/social_logos/twitter.svg';
import facebookAvatar from '../assets/social_logos/facebook.svg';
import vkAvatar from '../assets/social_logos/vk.svg';
import instagramAvatar from '../assets/social_logos/instagram.svg';
import telegramAvatar from '../assets/social_logos/telegram.svg';
import unknownNetwork from '../assets/social_logos/unknown-network.svg';
import unknownAvatar from '../assets/social_logos/unknown-avatar.svg';

export const ICONS_BY_SOCIAL_NETWORK: ReadonlyMap<string, string> = new Map([
['twitter', twitterAvatar],
['facebook', facebookAvatar],
['vk', vkAvatar],
['instagram', instagramAvatar],
['telegram', telegramAvatar],
]) as ReadonlyMap<string, string>;
export const ICONS_BY_SOCIAL_NETWORK: ReadonlyMap<string, string> = new Map([
['twitter', twitterAvatar],
['facebook', facebookAvatar],
['vk', vkAvatar],
['instagram', instagramAvatar],
['telegram', telegramAvatar],
]) as ReadonlyMap<string, string>;

@Component
export default class PublicationItemPlatformIcon extends Vue {
@Component
export default class PublicationItemPlatformIcon extends Vue {

@Prop({type: String})
public avatar?: string;
@Prop({type: String})
public avatar?: string;

@Prop({
type: String,
validator: (value) => {
return ICONS_BY_SOCIAL_NETWORK.has(value);
},
})
public socialType!: string;
@Prop({
type: String,
validator: (value) => {
return ICONS_BY_SOCIAL_NETWORK.has(value);
},
})
public socialType!: string;

get socialNetworkIcon(): string {
return ICONS_BY_SOCIAL_NETWORK.get(this.socialType) || unknownNetwork;
}
get socialNetworkIcon(): string {
return ICONS_BY_SOCIAL_NETWORK.get(this.socialType) || unknownNetwork;
}

get avatarIcon(): string {
return this.avatar || unknownAvatar;
}
get avatarIcon(): string {
return this.avatar || unknownAvatar;
}


}
}
</script>

<style scoped>
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ export interface PublicationInterface {
createdAt: string;
updatedAt: string;
}

export interface AuthResponseInterface {
access_token: string;
token_type: string;
expires_in: number;
refresh_token: string;
scope: string;
}
28 changes: 21 additions & 7 deletions src/router.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import Vue from 'vue';
import Router from 'vue-router';
import Publications from '@/views/Publications.vue';
import Login from '@/views/Login.vue';
import store from '@/store';

Vue.use(Router);

export default new Router({
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
Expand All @@ -15,12 +17,24 @@ export default new Router({
component: Publications,
},
{
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(/* webpackChunkName: "about" */ './views/About.vue'),
path: '/login',
name: 'login',
component: Login,
},
],
});

router.beforeEach((to, from, next) => {
const isAccessTokenExist = Boolean(store.getters.accessToken);
if (to.name === 'login' && isAccessTokenExist) {
next({path: '/'});
} else if (to.name === 'login' && !isAccessTokenExist) {
next();
} else if (isAccessTokenExist) {
next();
} else {
next({name: 'login'});
}
});

export default router;
16 changes: 11 additions & 5 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ Vue.use(Vuex);

export default new Vuex.Store({
state: {

accessToken: '',
refreshToken: '',
},
mutations: {

getters: {
accessToken: (state) => {
return state.accessToken;
},
},
actions: {

mutations: {
changeTokens(state, {accessToken, refreshToken}) {
state.accessToken = accessToken;
state.refreshToken = refreshToken;
},
},
});
64 changes: 64 additions & 0 deletions src/stories/LoginForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { withDocs } from 'storybook-readme';
import { storiesOf } from '@storybook/vue';
import { withKnobs, boolean, text } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';

import LoginForm from '../components/LoginForm';

storiesOf('LoginForm', module)
.addDecorator(withKnobs)
.addDecorator(withDocs(LoginForm.options.__docs))
.add('Login form', () => {
return {
components: { LoginForm },
props: {
error: {
default: text('Authorization error', '')
},
loading: {
default: boolean("Loading flag", false)
}
},
methods: {
change: action('submit-form-change-action'),
},
template: `<login-form
:error="error"
:loading="loading"
@change="change"
></login-form>`,
}
});

storiesOf('LoginForm', module)
.addDecorator(withKnobs)
.addDecorator(withDocs("Use `admin/admin` as right credentials. See story source code for more info."))
.add('Login form with handler', () => {
return {
components: { LoginForm },
data() {
return {
error: null,
loading: false,
}
},
methods: {
change(username, password) {
this.loading = true;
this.error = null;
setTimeout(() => {
b0g3r marked this conversation as resolved.
Show resolved Hide resolved
if (username !== 'admin' || password !== 'admin') {
this.error = 'Invalid credentials!'
}
this.loading = false;
}, 800);

}
},
template: `<login-form
:error="error"
:loading="loading"
@change="change"
></login-form>`,
}
});
Loading