Skip to content
This repository was archived by the owner on May 7, 2021. It is now read-only.

ユーザー招待ページ #94

Merged
merged 11 commits into from
Apr 11, 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
49 changes: 17 additions & 32 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"dependencies": {
"ajv": "^5.5.2",
"axios": "^0.18.0",
"bulma": "^0.7.4",
"vue": "^2.6.10",
"vue-router": "^3.0.1",
"vuex": "^3.0.1",
Expand Down
13 changes: 13 additions & 0 deletions src/api/invitation/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import api from '@/api';
import getHeader from '@/api/utils/get-header';

export default {
async listInvitations(sessionID) {
const res = (await api.client.get('/admin/invitations', getHeader(sessionID))).data.invitations;
return res;
},
async createInvitation(sessionID, email) {
const res = (await api.client.post('/admin/invitations', { email }, getHeader(sessionID))).data.invitations;
return res;
},
};
23 changes: 21 additions & 2 deletions src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ const router = new Router({
name: 'profile',
component: () => import(/* webpackChunkName: "profile" */ './views/memberIntroduction/Profile.vue'),
},
{
path: '/admin/invitations',
name: 'invitation',
component: () => import(/* webpackChunkName: "invitation" */ './views/admin/Invitation.vue'),
meta: { requiresAdmin: true },
},
],
});

Expand All @@ -101,11 +107,24 @@ router.beforeEach(async (to, from, next) => {
console.error(e);
store.commit('session/clearSessionID');
}

if (to.matched.some(record => record.meta.requiresAdmin) && !store.getters['user/isAdmin']) {
store.commit('criticalError/createError', {
response: {
status: 404,
data: {
message: 'Page not Found',
},
},
});
return;
}
if (to.matched.some(record => record.meta.requiresAuth) && !store.getters['session/loggedIn']) {
next({ path: '/login', query: { redirect: to.fullPath } });
} else {
next();
return;
}

next();
});

export default router;
2 changes: 2 additions & 0 deletions src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import achievement from './modules/achievement';
import emailConfirmations from './modules/emailConfirmations';
import memberIntroduction from './modules/memberIntroduction';
import editUser from './modules/editUser';
import invitation from './modules/invitation';

Vue.use(Vuex);

Expand All @@ -25,6 +26,7 @@ export default new Vuex.Store({
emailConfirmations,
memberIntroduction,
editUser,
invitation,
},
plugins: [
createPersistedState({
Expand Down
42 changes: 42 additions & 0 deletions src/store/modules/invitation/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import invitationClient from '@/api/invitation';

export default {
namespaced: true,
state: {
invitations: null,
invitationError: null,
},
/* eslint-disable no-param-reassign */
mutations: {
setInvitations(state, invitations) {
state.invitations = invitations;
},
clearInvitationError(state) {
state.invitationError = null;
},
setInvitationError(state, error) {
state.invitationError = error;
},
},
/* eslint-enable no-param-reassign */
actions: {
async listInvitations({ commit }, sessionID) {
try {
commit('setInvitations', await invitationClient.listInvitations(sessionID));
} catch (e) {
commit('criticalError/createError', e, { root: true });
}
},
async invite({ commit, dispatch }, { sessionID, rawEmails }) {
commit('clearInvitationError');
const emails = rawEmails.split(/\r\n|\n/).map(email => email.trim()).filter(email => email);
try {
await Promise.all(emails.map(email => invitationClient.createInvitation(sessionID, email)));
} catch (e) {
commit('setInvitationError', e);
}

dispatch('listInvitations', sessionID);
},
},
};
5 changes: 5 additions & 0 deletions src/store/modules/user/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,9 @@ export default {
}
},
},
getters: {
isAdmin(state) {
return state.userData && state.userData.authority === 'ADMIN';
},
},
};
77 changes: 77 additions & 0 deletions src/views/admin/Invitation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<div class="container">
<section class="section">
<h1 class="title">部員の招待</h1>
<div>
<form v-on:submit.prevent="onInvite">
<div class="field">
<label class="label">メールアドレス</label>
<div class="control">
<textarea
class="textarea"
:placeholder="'[email protected]\[email protected]\[email protected]\n...'"
v-model="emails"
></textarea>
</div>
</div>
<div class="control">
<button type="submit" class="button">招待</button>
</div>
<ErrorMessage :error="invitationError"/>
</form>
</div>
</section>
<section class="section">
<h1 class="title">招待されたメールアドレス</h1>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>メールアドレス</th>
</tr>
</thead>
<tbody>
<tr v-for="invitation in invitations" :key="invitation.invitation_id">
<th>{{ invitation.invitation_id }}</th>
<td>{{ invitation.email }}</td>
</tr>
</tbody>
</table>
</section>
</div>
</template>

<script>
import 'bulma/css/bulma.css';
import { mapState, mapActions } from 'vuex';
import ErrorMessage from '@/components/ErrorMessage.vue';

export default {
name: 'invitation',
components: {
ErrorMessage,
},
data() {
return {
emails: null,
};
},
created() {
this.listInvitations(this.sessionID);
},
computed: {
...mapState('invitation', ['invitations', 'invitationError']),
...mapState('session', ['sessionID']),
},
methods: {
...mapActions('invitation', ['listInvitations', 'invite']),
onInvite() {
this.invite({ sessionID: this.sessionID, rawEmails: this.emails });
this.emails = null;
},
},
};
</script>

<style>
</style>