Skip to content

Commit c0baf13

Browse files
committed
implement board page
1 parent 359dfbd commit c0baf13

File tree

81 files changed

+2516
-78
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+2516
-78
lines changed

front-end/package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

front-end/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
"i": "^0.3.6",
2121
"jquery": "^3.3.1",
2222
"lodash": "^4.17.10",
23+
"noty": "^3.2.0-beta",
2324
"npm": "^6.4.0",
2425
"popper.js": "^1.14.4",
2526
"vue": "^2.5.17",
2627
"vue-i18n": "^8.0.0",
2728
"vue-router": "^3.0.1",
29+
"vuedraggable": "^2.16.0",
2830
"vuelidate": "^0.7.4",
2931
"vuex": "^3.0.1"
3032
},

front-end/src/App.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,21 @@ export default {
1313
</script>
1414

1515
<style lang="scss">
16-
html {
16+
html, body {
17+
height: 100%;
1718
font-size: 14px;
1819
}
1920
21+
#app, .page {
22+
height: 100%;
23+
position: relative;
24+
}
25+
26+
.page {
27+
display: flex;
28+
flex-direction: column;
29+
}
30+
2031
.public.container {
2132
max-width: 900px;
2233
}
@@ -26,6 +37,10 @@ textarea.form-control:focus {
2637
border: 1px solid #377EF6 !important;
2738
}
2839
40+
.btn-cancel {
41+
color: #666 !important;
42+
}
43+
2944
.public {
3045
.form {
3146
margin-top: 50px;

front-end/src/components/PageHeader.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
</button>
3838
<div class="dropdown-menu" aria-labelledby="profileMenu">
3939
<button class="dropdown-item" type="button">{{ $t('header.profile') }}</button>
40-
<button class="dropdown-item" type="button">{{ $t('header.signOut') }}</button>
40+
<button class="dropdown-item" type="button" @click="signOut()">{{ $t('header.signOut') }}</button>
4141
</div>
4242
</div>
4343
</div>
@@ -47,6 +47,8 @@
4747
<script>
4848
import 'bootstrap/dist/js/bootstrap.min'
4949
import { mapGetters } from 'vuex'
50+
import meService from '@/services/me'
51+
import notify from '@/utils/notify'
5052
5153
export default {
5254
name: 'PageHeader',
@@ -67,13 +69,21 @@ export default {
6769
},
6870
openBoard (board) {
6971
this.$router.push({name: 'board', params: { boardId: board.id }})
72+
},
73+
signOut () {
74+
meService.signOut().then(() => {
75+
this.$router.push({name: 'login'})
76+
}).catch(error => {
77+
notify.error(error.message)
78+
})
7079
}
7180
}
7281
}
7382
</script>
7483

7584
<style lang="scss" scoped>
7685
.page-header {
86+
flex: none;
7787
padding: 9px 10px 8px;
7888
border-bottom: 1px solid #eee;
7989

front-end/src/locale/messages/en_US.json

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,36 @@
3232
"form": {
3333
"username": {
3434
"label": "Username",
35-
"requred": "Username is required",
35+
"required": "Username is required",
3636
"alphaNum": "Username can only contain letters and numbers",
3737
"minLength": "Username must have at least {minLength} letters.",
38-
"maxLength": "Username is too long. It can contains maximium {maxLength} letters."
38+
"maxLength": "Username is too long. It can contains maximum {maxLength} letters."
3939
},
4040
"emailAddress": {
4141
"label": "Email address",
4242
"required": "Email address is required",
4343
"email": "This is not a valid email address",
44-
"maxLength": "Email address is too long. It can contains maximium {maxLength} letters."
44+
"maxLength": "Email address is too long. It can contains maximum {maxLength} letters."
45+
},
46+
"firstName": {
47+
"label": "First name",
48+
"required": "First name is required",
49+
"alpha": "First name can only contain letters",
50+
"minLength": "First name must have at least {minLength} letters.",
51+
"maxLength": "First name is too long. It can contains maximum {maxLength} letters."
52+
},
53+
"lastName": {
54+
"label": "Last name",
55+
"required": "Last name is required",
56+
"alpha": "Last name can only contain letters",
57+
"minLength": "Last name must have at least {minLength} letters.",
58+
"maxLength": "Last name is too long. It can contains maximum {maxLength} letters."
4559
},
4660
"password": {
4761
"label": "Password",
4862
"required": "Password is required",
4963
"minLength": "Password is too short. It can contains at least {minLength} letters.",
50-
"maxLength": "Password is too long. It can contains maximium {maxLength} letters."
64+
"maxLength": "Password is too long. It can contains maximum {maxLength} letters."
5165
},
5266
"submit": "Create account",
5367
"terms": {

front-end/src/main.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import store from './store'
55
import axios from 'axios'
66
import Vuelidate from 'vuelidate'
77
import { library as faLibrary } from '@fortawesome/fontawesome-svg-core'
8-
import { faHome, faSearch, faPlus } from '@fortawesome/free-solid-svg-icons'
8+
import { faHome, faSearch, faPlus, faEllipsisH, faUserPlus, faListUl } from '@fortawesome/free-solid-svg-icons'
99
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
1010
import { i18n } from './i18n'
1111

@@ -23,7 +23,7 @@ axios.interceptors.response.use(
2323
Vue.use(Vuelidate)
2424

2525
// Set up FontAwesome
26-
faLibrary.add(faHome, faSearch, faPlus)
26+
faLibrary.add(faHome, faSearch, faPlus, faEllipsisH, faUserPlus, faListUl)
2727
Vue.component('font-awesome-icon', FontAwesomeIcon)
2828

2929
Vue.config.productionTip = false
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<template>
2+
<form @submit.prevent="addMember">
3+
<div class="modal" tabindex="-1" role="dialog" backdrop="static" id="addMemberModal">
4+
<div class="modal-dialog" role="document">
5+
<div class="modal-content">
6+
<div class="modal-header">
7+
<h5 class="modal-title">Add Member</h5>
8+
<button type="button" class="close" @click="close" aria-label="Close">
9+
<span aria-hidden="true">&times;</span>
10+
</button>
11+
</div>
12+
<div class="modal-body">
13+
<div v-show="errorMessage" class="alert alert-danger failed">{{ errorMessage }}</div>
14+
<div class="form-group">
15+
<input type="text" class="form-control" id="usernameOrEmailAddressInput" v-model="usernameOrEmailAddress" placeholder="Username or email address" maxlength="128">
16+
<div class="field-error" v-if="$v.usernameOrEmailAddress.$dirty">
17+
<div class="error" v-if="!$v.usernameOrEmailAddress.required">This is required</div>
18+
</div>
19+
</div>
20+
</div>
21+
<div class="modal-footer">
22+
<button type="submit" class="btn btn-primary">Add</button>
23+
<button type="button" class="btn btn-default btn-cancel" @click="close">Cancel</button>
24+
</div>
25+
</div>
26+
</div>
27+
</div>
28+
</form>
29+
</template>
30+
31+
<script>
32+
import $ from 'jquery'
33+
import { required } from 'vuelidate/lib/validators'
34+
import boardService from '@/services/boards'
35+
36+
export default {
37+
name: 'AddMemberModal',
38+
props: ['boardId'],
39+
data () {
40+
return {
41+
usernameOrEmailAddress: '',
42+
errorMessage: ''
43+
}
44+
},
45+
validations: {
46+
usernameOrEmailAddress: {
47+
required
48+
}
49+
},
50+
mounted () {
51+
$('#addMemberModal').on('shown.bs.modal', () => {
52+
$('#usernameOrEmailAddressInput').trigger('focus')
53+
})
54+
},
55+
methods: {
56+
addMember () {
57+
this.$v.$touch()
58+
if (this.$v.$invalid) {
59+
return
60+
}
61+
62+
boardService.addMember(this.boardId, this.usernameOrEmailAddress).then((member) => {
63+
this.$emit('added', member)
64+
this.close()
65+
}).catch(error => {
66+
this.errorMessage = error.message
67+
})
68+
},
69+
close () {
70+
this.$v.$reset()
71+
this.usernameOrEmailAddress = ''
72+
this.errorMessage = ''
73+
$('#addMemberModal').modal('hide')
74+
}
75+
}
76+
}
77+
</script>
78+
79+
<style lang="scss" scoped>
80+
.modal {
81+
.modal-dialog {
82+
width: 300px;
83+
}
84+
}
85+
</style>

front-end/src/services/boards.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,32 @@ export default {
1414
reject(errorParser.parse(error))
1515
})
1616
})
17+
},
18+
/**
19+
* Add user to a board
20+
* @param {*} boardId the id of the board
21+
* @param {*} usernameOrEmailAddress user's username or email address
22+
*/
23+
addMember (boardId, usernameOrEmailAddress) {
24+
return new Promise((resolve, reject) => {
25+
axios.post('/boards/' + boardId + '/members', { usernameOrEmailAddress }).then(({data}) => {
26+
resolve(data)
27+
}).catch((error) => {
28+
reject(errorParser.parse(error))
29+
})
30+
})
31+
},
32+
/**
33+
* Get a board and everything under it
34+
* @param {*} boardId the id of the board
35+
*/
36+
getBoard (boardId) {
37+
return new Promise((resolve, reject) => {
38+
axios.get('/boards/' + boardId).then(({data}) => {
39+
resolve(data)
40+
}).catch((error) => {
41+
reject(errorParser.parse(error))
42+
})
43+
})
1744
}
1845
}

front-end/src/services/card-lists.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import axios from 'axios'
2+
import errorParser from '@/utils/error-parser'
3+
4+
export default {
5+
/**
6+
* Add a new card list
7+
* @param {*} detail the card list detail
8+
*/
9+
add (detail) {
10+
return new Promise((resolve, reject) => {
11+
axios.post('/card-lists', detail).then(({data}) => {
12+
resolve(data)
13+
}).catch((error) => {
14+
reject(errorParser.parse(error))
15+
})
16+
})
17+
},
18+
changePositions (positionChanges) {
19+
return new Promise((resolve, reject) => {
20+
axios.post('/card-lists/positions', positionChanges).then(({data}) => {
21+
resolve(data)
22+
}).catch((error) => {
23+
reject(errorParser.parse(error))
24+
})
25+
})
26+
}
27+
}

front-end/src/services/cards.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import axios from 'axios'
2+
import errorParser from '@/utils/error-parser'
3+
4+
export default {
5+
/**
6+
* Add a new card
7+
* @param {*} detail the card detail
8+
*/
9+
add (detail) {
10+
return new Promise((resolve, reject) => {
11+
axios.post('/cards', detail).then(({data}) => {
12+
resolve(data)
13+
}).catch((error) => {
14+
reject(errorParser.parse(error))
15+
})
16+
})
17+
},
18+
changePositions (positionChanges) {
19+
return new Promise((resolve, reject) => {
20+
axios.post('/cards/positions', positionChanges).then(({data}) => {
21+
resolve(data)
22+
}).catch((error) => {
23+
reject(errorParser.parse(error))
24+
})
25+
})
26+
}
27+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy