clean tests & other things

This commit is contained in:
2020-03-25 20:11:29 +01:00
parent 11511039e9
commit 7e5d022aa2
27 changed files with 1497 additions and 187 deletions

45
.eslintrc.json Normal file
View File

@@ -0,0 +1,45 @@
{
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": [
"eslint:recommended",
"plugin:vue/essential",
"plugin:vue/base",
"plugin:vue/strongly-recommended",
"plugin:vue/recommended"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"vue"
],
"rules": {
"indent": [
"off",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"off",
"always"
],
"no-console": "off",
"strict": "off"
}
}

8
.idea/laravel-plugin.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="LaravelPluginSettings">
<option name="pluginEnabled" value="true" />
<option name="routerNamespace" value="" />
<option name="mainLanguage" value="en" />
</component>
</project>

View File

@@ -11,6 +11,13 @@ use Symfony\Component\HttpFoundation\Response;
class UserController extends Controller
{
public function index()
{
$this->authorize('viewAny', User::class);
return UserResource::collection(User::all());
}
public function store()
{
$this->authorize('create', User::class);

View File

@@ -4,6 +4,7 @@ namespace App;
use App\Models\Memo;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
@@ -39,12 +40,12 @@ class User extends Authenticatable
'email_verified_at' => 'datetime',
];
public function isAdmin()
public function isAdmin(): bool
{
return $this->role === 2;
}
public function memos()
public function memos() : HasMany
{
return $this->hasMany(Memo::class);
}

View File

@@ -17,7 +17,7 @@ class UserPolicy
*/
public function viewAny(User $user)
{
return true;
return $user->isAdmin();
}
/**

869
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,12 +7,18 @@
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"eslint": "./node_modules/.bin/eslint resources/assets/js/ test/ --ext .js,.vue"
},
"devDependencies": {
"axios": "^0.19",
"babel-eslint": "^10.1.0",
"cross-env": "^7.0",
"eslint": "^6.8.0",
"eslint-loader": "^3.0.3",
"eslint-plugin-vue": "^6.2.2",
"laravel-mix": "^5.0.1",
"laravel-mix-eslint": "^0.1.3",
"lodash": "^4.17.13",
"resolve-url-loader": "^2.3.1",
"sass": "^1.20.1",

63
public/css/app.css vendored
View File

@@ -1,4 +1,4 @@
@import url(https://fonts.googleapis.com/css?family=Nunito);@import url(https://fonts.googleapis.com/css?family=Open+Sans);body {
@import url(https://fonts.googleapis.com/css?family=Open+Sans);body {
margin: 0;
}
@@ -68,6 +68,7 @@ body {
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.flex-end {
@@ -435,21 +436,26 @@ body {
}
.btn,
.btn-alert,
.btn-secondary,
.btn-primary {
font-size: 1.6rem;
border: 1px solid transparent;
padding: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
cursor: pointer;
box-shadow: 1px 1px 2px grey;
text-decoration: none;
transition: background-color 0.2s, color 0.2s;
}
.btn:hover,
.btn-alert:hover,
.btn-secondary:hover,
.btn-primary:hover {
transition: background-color 0.2s, color 0.2s;
background-color: #1F6521;
color: #ffffff;
}
.btn-primary {
@@ -458,35 +464,43 @@ body {
color: #ffffff;
}
.btn-primary:hover {
background-color: #1F2605;
color: #D6CE15;
.btn-primary:visited,
.btn-primary:focus {
color: #ffffff;
}
.btn-secondary {
background-color: #A4A71E;
border-color: #53900F;
border-color: #A4A71E;
color: #1F2605;
}
.btn-secondary:hover {
.btn-alert {
background-color: red;
border-color: red;
color: #ffffff;
}
.btn-alert:hover {
background-color: #1F2605;
color: #D6CE15;
}
label {
display: block;
}
textarea,
input {
width: 100%;
border: 1px solid #D6CE15;
background-color: #eeeeee;
font-size: 1.6rem;
font-family: "Open Sans", sans-serif;
padding: 0.5rem;
border-radius: 0.5rem;
}
textarea:focus,
input:focus {
background-color: #ffffff;
border-color: #1F2605;
@@ -496,6 +510,15 @@ input[type=checkbox] {
width: unset;
}
a {
color: #1F6521;
}
a:focus,
a:visited {
color: #1F2605;
}
body {
overflow: hidden;
height: 100vh;
@@ -603,3 +626,27 @@ aside {
font-weight: bold;
}
.memo-list h1 {
font-size: 2.2rem;
}
.memo-list a {
text-decoration: none;
}
.memo-list:nth-child(even) {
background-color: #D6CE15;
}
.memo-list:hover {
background-color: #A4A71E;
}
.memo-list:hover .memo-date {
opacity: 1;
}
.memo-date {
opacity: 0.6;
}

251
public/js/app.js vendored
View File

@@ -1912,9 +1912,25 @@ __webpack_require__.r(__webpack_exports__);
//
//
//
//
//
//
//
//
/* harmony default export */ __webpack_exports__["default"] = ({
name: "AlertBox",
props: ['type', 'message']
props: {
type: {
type: String,
required: true,
"default": 'success'
},
message: {
type: String,
required: true,
"default": 'message'
}
}
});
/***/ }),
@@ -2247,6 +2263,7 @@ __webpack_require__.r(__webpack_exports__);
//
//
//
//
/* harmony default export */ __webpack_exports__["default"] = ({
name: "CssTesteur"
});
@@ -2535,12 +2552,55 @@ __webpack_require__.r(__webpack_exports__);
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// import TagBox from "../Tag/TagBox";
var MarkdownIt = __webpack_require__(/*! markdown-it */ "./node_modules/markdown-it/index.js"),
md = new MarkdownIt();
/* harmony default export */ __webpack_exports__["default"] = ({
name: "MemoShow",
name: 'MemoShow',
components: {// TagBox
},
data: function data() {
@@ -2555,31 +2615,31 @@ var MarkdownIt = __webpack_require__(/*! markdown-it */ "./node_modules/markdown
return md.render(this.memo.memo);
}
},
methods: {
destroy: function destroy() {
mounted: function mounted() {
var _this = this;
axios["delete"]('/api/memos/' + this.$route.params.id).then(function (response) {
axios.get('/api/memos/' + this.$route.params.id).then(function (response) {
_this.memo = response.data.data;
_this.loading = false;
})["catch"](function (errorRes) {
_this.loading = false;
if (errorRes.response.status === 404) {
_this.$router.push('/memos');
})["catch"](function (error) {
alert('Internal Error, Unable to delete contact.');
});
}
});
},
mounted: function mounted() {
methods: {
destroy: function destroy() {
var _this2 = this;
axios.get('/api/memos/' + this.$route.params.id).then(function (response) {
_this2.memo = response.data.data;
_this2.loading = false;
})["catch"](function (error) {
_this2.loading = false;
if (error.response.status === 404) {
axios["delete"]('/api/memos/' + this.$route.params.id).then(function (response) {
_this2.$router.push('/memos');
}
})["catch"](function (errorRes) {
alert('Internal Error, Unable to delete contact.' + errorRes);
});
}
}
});
/***/ }),
@@ -2627,12 +2687,43 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
/* harmony default export */ __webpack_exports__["default"] = ({
name: "UserAdmin",
name: 'UserAdmin',
components: {
Avatar: _components_Avatar__WEBPACK_IMPORTED_MODULE_1__["default"],
AlertBox: _components_AlertBox__WEBPACK_IMPORTED_MODULE_2__["default"],
@@ -2646,15 +2737,28 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
},
alertType: '',
alertMessage: '',
errors: null
errors: null,
loading: '',
users: null
};
},
computed: _objectSpread({}, Object(vuex__WEBPACK_IMPORTED_MODULE_0__["mapGetters"])({
authUser: 'authUser'
})),
mounted: function mounted() {
var _this = this;
axios.get('/api/users').then(function (response) {
_this.users = response.data.data;
_this.loading = false;
})["catch"](function (error) {
_this.loading = false;
alert('Unable to fetch users.');
});
},
methods: {
addMember: function addMember() {
var _this = this;
var _this2 = this;
console.log('addMember');
@@ -2664,18 +2768,18 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
email: this.form.email
}).then(function (res) {
console.log(res);
_this.form.name = '';
_this.form.email = '';
_this.alertType = 'success';
_this.alertMessage = "".concat(res.data.data.attributes.name, " a bien \xE9t\xE9 cr\xE9\xE9");
_this2.form.name = '';
_this2.form.email = '';
_this2.alertType = 'success';
_this2.alertMessage = "".concat(res.data.data.attributes.name, " a bien \xE9t\xE9 cr\xE9\xE9");
})["catch"](function (errors) {
console.log(errors);
_this.alertType = 'error';
_this.alertMessage = "L'utilisateur n'a pas \xE9t\xE9 cr\xE9\xE9";
_this2.alertType = 'error';
_this2.alertMessage = 'L\'utilisateur n\'a pas été créé';
});
} else {
this.alertType = 'error';
this.alertMessage = "Le formulaire n'est pas correctement renseign\xE9.";
this.alertMessage = 'Le formulaire n\'est pas correctement renseigné.';
}
}
}
@@ -2707,7 +2811,7 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
//
/* harmony default export */ __webpack_exports__["default"] = ({
name: "Profil",
name: 'Profil',
computed: _objectSpread({}, Object(vuex__WEBPACK_IMPORTED_MODULE_0__["mapGetters"])({
authUser: 'authUser'
}))
@@ -29114,7 +29218,7 @@ var render = function() {
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", { staticClass: "p-1", class: "alert-" + _vm.type }, [
_vm._v(_vm._s(_vm.message))
_vm._v("\n " + _vm._s(_vm.message) + "\n")
])
}
var staticRenderFns = []
@@ -29662,15 +29766,18 @@ var staticRenderFns = [
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("form", [
_c("label", [_vm._v("Test label")]),
_c("label", { staticClass: "mb-1" }, [_vm._v("Test label")]),
_vm._v(" "),
_c("input", { attrs: { type: "text", placeholder: "Test placeholder" } }),
_c("input", {
staticClass: "mb-1",
attrs: { type: "text", placeholder: "Test placeholder" }
}),
_vm._v(" "),
_c("input", { attrs: { type: "number" } }),
_c("input", { staticClass: "mb-1", attrs: { type: "number" } }),
_vm._v(" "),
_c("input", { attrs: { type: "checkbox" } }),
_c("input", { staticClass: "mb-1", attrs: { type: "checkbox" } }),
_vm._v(" "),
_c("select", [
_c("select", { staticClass: "mb-1" }, [
_c("option", [_vm._v("test 1")]),
_vm._v(" "),
_c("option", [_vm._v("test 2")]),
@@ -29678,7 +29785,13 @@ var staticRenderFns = [
_c("option", [_vm._v("test 3")])
]),
_vm._v(" "),
_c("input", { attrs: { type: "submit" } })
_c(
"textarea",
{ staticClass: "mb-1", attrs: { cols: "30", rows: "10" } },
[_vm._v("Texte exemple")]
),
_vm._v(" "),
_c("input", { staticClass: "mb-1", attrs: { type: "submit" } })
])
}
]
@@ -29961,7 +30074,7 @@ var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", [
return _c("div", { staticClass: "p-2" }, [
_c(
"div",
{ staticClass: "flex-between flex-center mb-1" },
@@ -29969,7 +30082,6 @@ var render = function() {
_c(
"a",
{
staticClass: "link",
attrs: { href: "#" },
on: {
click: function($event) {
@@ -30012,11 +30124,12 @@ var render = function() {
_vm._l(_vm.memos, function(memo) {
return _c(
"div",
{ staticClass: "memo-list" },
[
_c(
"router-link",
{
staticClass: "link-large relative flex-center p-2",
staticClass: "relative flex-center flex-between p-1",
attrs: { to: "/memos/" + memo.data.memo_id }
},
[
@@ -30058,11 +30171,11 @@ var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", [
return _c("div", { staticClass: "p-2" }, [
_vm.loading
? _c("div", [_vm._v("> Loading...")])
? _c("div", [_vm._v("\n > Loading...\n ")])
: _c("div", [
_c("div", { staticClass: "flex-between mb-2" }, [
_c("div", { staticClass: "flex-between flex-center mb-1" }, [
_c(
"a",
{
@@ -30074,7 +30187,7 @@ var render = function() {
}
}
},
[_vm._v("\n < Back\n ")]
[_vm._v("< Back")]
),
_vm._v(" "),
_c(
@@ -30084,10 +30197,10 @@ var render = function() {
_c(
"router-link",
{
staticClass: "btn-success mr-1",
staticClass: "btn-secondary mr-1",
attrs: { to: "/memos/" + _vm.memo.memo_id + "/edit" }
},
[_vm._v("Edit")]
[_vm._v("\n Edit\n ")]
),
_vm._v(" "),
_c(
@@ -30101,7 +30214,7 @@ var render = function() {
}
}
},
[_vm._v("Delete")]
[_vm._v("\n Delete\n ")]
),
_vm._v(" "),
_vm.modal
@@ -30121,7 +30234,7 @@ var render = function() {
}
}
},
[_vm._v("Cancel")]
[_vm._v("\n Cancel\n ")]
),
_vm._v(" "),
_c(
@@ -30130,7 +30243,7 @@ var render = function() {
staticClass: "btn-alert-strong",
on: { click: _vm.destroy }
},
[_vm._v("Delete")]
[_vm._v("\n Delete\n ")]
)
])
])
@@ -30151,10 +30264,12 @@ var render = function() {
: _vm._e()
]),
_vm._v(" "),
_c("p", { staticClass: "title-section pt-3" }, [_vm._v("Memo")]),
_c("p", { staticClass: "title-section pt-3" }, [
_vm._v("\n Memo\n ")
]),
_vm._v(" "),
_c("h1", { staticClass: "memo-title" }, [
_vm._v(_vm._s(_vm.memo.name))
_vm._v("\n " + _vm._s(_vm.memo.name) + "\n ")
]),
_vm._v(" "),
_c("p", {
@@ -30163,7 +30278,11 @@ var render = function() {
}),
_vm._v(" "),
_c("div", { staticClass: "memo-change my-2 p-1" }, [
_vm._v("@last update : " + _vm._s(_vm.memo.last_updated))
_vm._v(
"\n @last update : " +
_vm._s(_vm.memo.last_updated) +
"\n "
)
])
])
])
@@ -30222,7 +30341,9 @@ var render = function() {
? _c(
"div",
[
_c("h2", { staticClass: "mb-1" }, [_vm._v("Ajouter un membre")]),
_c("h2", { staticClass: "mb-1" }, [
_vm._v("\n Ajouter un membre\n ")
]),
_vm._v(" "),
_vm.alertType
? _c("AlertBox", {
@@ -30279,7 +30400,33 @@ var render = function() {
],
1
)
: _vm._e()
: _vm._e(),
_vm._v(" "),
_c("div", [
_c("h2", [_vm._v("Liste des utilisateurs")]),
_vm._v(" "),
_c(
"ul",
[
_vm.loading
? _c("li", [_vm._v("> Loading...")])
: _vm._l(_vm.users, function(user) {
return _c("li", [
_c("a", { attrs: { href: user.links.self } }, [
_vm._v(_vm._s(user.data.attributes.name))
]),
_vm._v(
" - " +
_vm._s(user.data.attributes.email) +
" - " +
_vm._s(user.data.attributes.is_admin)
)
])
})
],
2
)
])
])
}
var staticRenderFns = []

View File

@@ -0,0 +1,22 @@
/*!
* Vue.js v2.6.11
* (c) 2014-2019 Evan You
* Released under the MIT License.
*/
/*! https://mths.be/punycode v1.4.1 by @mathias */
/**
* @license
* Lodash <https://lodash.com/>
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/**
* vuex v3.1.3
* (c) 2020 Evan You
* @license MIT
*/

View File

@@ -1,10 +1,26 @@
<template>
<div v-bind:class="'alert-' + type" class="p-1">{{ message }}</div>
<div
v-bind:class="'alert-' + type"
class="p-1"
>
{{ message }}
</div>
</template>
<script>
export default {
name: "AlertBox",
props: ['type', 'message']
props: {
type: {
type: String,
required: true,
default: 'success',
},
message: {
type: String,
required: true,
default: 'message',
},
}
}
</script>

View File

@@ -21,16 +21,17 @@
<a href="#" class="btn-alert">X</a>
</div>
<form>
<label>Test label</label>
<input type="text" placeholder="Test placeholder">
<input type="number">
<input type="checkbox">
<select>
<label class="mb-1">Test label</label>
<input type="text" placeholder="Test placeholder" class="mb-1">
<input type="number" class="mb-1">
<input type="checkbox" class="mb-1">
<select class="mb-1">
<option>test 1</option>
<option>test 2</option>
<option>test 3</option>
</select>
<input type="submit">
<textarea cols="30" rows="10" class="mb-1">Texte exemple</textarea>
<input type="submit" class="mb-1">
</form>
</div>
</template>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div class="p-2">
<div class="flex-between flex-center mb-1">
<a href="#" @click="$router.back()" class="link">
<a href="#" @click="$router.back()">
< Back
</a>
<router-link :to="'/memos/create'" class="btn-primary">Add New Memo</router-link>
@@ -11,8 +11,8 @@
<div v-if="memos.lenght === 0">
<p>No memos yet. <router-link to="/memos/create">Get Started ></router-link></p>
</div>
<div v-for="memo in memos">
<router-link :to="'/memos/' + memo.data.memo_id" class="link-large relative flex-center p-2">
<div v-for="memo in memos" class="memo-list">
<router-link :to="'/memos/' + memo.data.memo_id" class="relative flex-center flex-between p-1">
<h1>{{ memo.data.name }}</h1>
<div class="memo-date">{{ memo.data.last_updated }}</div>
</router-link>

View File

@@ -1,29 +1,72 @@
<template>
<div>
<div v-if="loading">> Loading...</div>
<div class="p-2">
<div
v-if="loading"
>
> Loading...
</div>
<div v-else>
<div class="flex-between mb-2">
<a href="#" @click="$router.back()" class="link">
< Back
</a>
<div class="flex-between flex-center mb-1">
<a
href="#"
class="link"
@click="$router.back()"
>< Back</a>
<div class="relative">
<router-link :to="'/memos/' + memo.memo_id + '/edit'" class="btn-success mr-1">Edit</router-link>
<a href="#" @click="modal = ! modal" class="btn-alert">Delete</a>
<div v-if="modal" class="absolute modal mt-2">
<router-link
:to="'/memos/' + memo.memo_id + '/edit'"
class="btn-secondary mr-1"
>
Edit
</router-link>
<a
href="#"
class="btn-alert"
@click="modal = ! modal"
>
Delete
</a>
<div
v-if="modal"
class="absolute modal mt-2"
>
<p>Are you sure you want to delete this record ?</p>
<div class="flex-end flex-center mt-2">
<button @click="modal = ! modal" class="btn mr-2">Cancel</button>
<button @click="destroy" class="btn-alert-strong">Delete</button>
<button
class="btn mr-2"
@click="modal = ! modal"
>
Cancel
</button>
<button
class="btn-alert-strong"
@click="destroy"
>
Delete
</button>
</div>
</div>
</div>
<div v-if="modal" @click="modal = ! modal" class="modal-background"></div>
<div
v-if="modal"
class="modal-background"
@click="modal = ! modal"
/>
</div>
<!-- <TagBox :memo="memo" />-->
<p class="title-section pt-3">Memo</p>
<h1 class="memo-title">{{ memo.name }}</h1>
<p class="memo-style pt-1" v-html="memoMarkdown"> </p>
<div class="memo-change my-2 p-1">@last update : {{ memo.last_updated }}</div>
<p class="title-section pt-3">
Memo
</p>
<h1 class="memo-title">
{{ memo.name }}
</h1>
<p
class="memo-style pt-1"
v-html="memoMarkdown"
></p>
<div class="memo-change my-2 p-1">
@last update : {{ memo.last_updated }}
</div>
</div>
</div>
</template>
@@ -34,7 +77,7 @@
md = new MarkdownIt();
export default {
name: "MemoShow",
name: 'MemoShow',
components: {
// TagBox
},
@@ -50,29 +93,29 @@
return md.render(this.memo.memo)
}
},
methods: {
destroy: function () {
axios.delete('/api/memos/' + this.$route.params.id)
.then(response => {
this.$router.push('/memos')
})
.catch(error => {
alert('Internal Error, Unable to delete contact.')
})
}
},
mounted() {
axios.get('/api/memos/' + this.$route.params.id)
.then(response => {
this.memo = response.data.data
this.loading = false
})
.catch(error => {
.catch(errorRes => {
this.loading = false
if (error.response.status === 404) {
if (errorRes.response.status === 404) {
this.$router.push('/memos')
}
})
},
methods: {
destroy: function () {
axios.delete('/api/memos/' + this.$route.params.id)
.then(response => {
this.$router.push('/memos')
})
.catch(errorRes => {
alert('Internal Error, Unable to delete contact.' + errorRes)
})
}
}
}
</script>

View File

@@ -2,7 +2,11 @@
<div>
<div class="flex mb-4">
<div class="avatar mr-2">
<Avatar :avatar="authUser.data.attributes.avatar" size="large" :alt="authUser.data.attributes.name" />
<Avatar
:avatar="authUser.data.attributes.avatar"
size="large"
:alt="authUser.data.attributes.name"
/>
</div>
<div class="flex-col flex-center">
<div><strong>{{ authUser.data.attributes.name }}</strong></div>
@@ -10,26 +14,53 @@
</div>
</div>
<div v-if="authUser.data.attributes.is_admin">
<h2 class="mb-1">Ajouter un membre</h2>
<AlertBox v-if="alertType" :type="alertType" :message="alertMessage" class="mb-1" />
<h2 class="mb-1">
Ajouter un membre
</h2>
<AlertBox
v-if="alertType"
:type="alertType"
:message="alertMessage"
class="mb-1"
/>
<form @submit.prevent="addMember">
<InputField name="name" type="text" label="Nom du nouveau membre" placeholder="Nom" @update:field="form.name = $event" :errors="errors" />
<InputField name="email" type="email" label="Adresse email du nouveau membre" placeholder="E-mail" @update:field="form.email = $event" :errors="errors" />
<InputField
name="name"
type="text"
label="Nom du nouveau membre"
placeholder="Nom"
:errors="errors"
@update:field="form.name = $event"
/>
<InputField
name="email"
type="email"
label="Adresse email du nouveau membre"
placeholder="E-mail"
:errors="errors"
@update:field="form.email = $event"
/>
<button>Ajouter</button>
</form>
</div>
<div>
<h2>Liste des utilisateurs</h2>
<ul>
<li v-if="loading">> Loading...</li>
<li v-else v-for="user in users"><a :href="user.links.self">{{ user.data.attributes.name }}</a> - {{ user.data.attributes.email }} - {{ user.data.attributes.is_admin }}</li>
</ul>
</div>
</div>
</template>
<script>
import {mapGetters} from "vuex";
import Avatar from "../../components/Avatar";
import AlertBox from "../../components/AlertBox";
import InputField from "../../components/InputField";
import {mapGetters} from 'vuex';
import Avatar from '../../components/Avatar';
import AlertBox from '../../components/AlertBox';
import InputField from '../../components/InputField';
export default {
name: "UserAdmin",
name: 'UserAdmin',
components: {
Avatar, AlertBox, InputField,
},
@@ -42,6 +73,8 @@
alertType: '',
alertMessage: '',
errors: null,
loading: '',
users: null,
}
},
computed: {
@@ -49,6 +82,17 @@
authUser: 'authUser',
})
},
mounted() {
axios.get('/api/users')
.then(response => {
this.users = response.data.data
this.loading = false
})
.catch(error => {
this.loading = false
alert('Unable to fetch users.')
})
},
methods: {
addMember: function () {
console.log('addMember')
@@ -64,11 +108,11 @@
.catch(errors => {
console.log(errors)
this.alertType = 'error'
this.alertMessage = `L'utilisateur n'a pas été créé`
this.alertMessage = 'L\'utilisateur n\'a pas été créé'
})
} else {
this.alertType = 'error'
this.alertMessage = `Le formulaire n'est pas correctement renseigné.`
this.alertMessage = 'Le formulaire n\'est pas correctement renseigné.'
}
}
}

View File

@@ -8,7 +8,7 @@
import { mapGetters } from 'vuex'
export default {
name: "Profil",
name: 'Profil',
computed: {
...mapGetters({
authUser: 'authUser',

View File

@@ -1,5 +1,3 @@
// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');
@import "setup/reset";
@import "setup/colors";
@@ -15,3 +13,4 @@
@import "components/alert_box";
@import "pages/auth";
@import "pages/memos";

View File

@@ -3,14 +3,17 @@
.btn {
font-size: 1.6rem;
border: 1px solid transparent;
padding: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
cursor: pointer;
box-shadow: 1px 1px 2px $grey;
text-decoration: none;
transition: background-color 0.2s, color 0.2s;
&:hover {
transition: background-color 0.2s, color 0.2s;
background-color: $mediumDark;
color: $white;
}
}
@@ -20,20 +23,26 @@
border-color: $medium;
color: $white;
&:hover {
background-color: $dark;
color: $light;
&:visited,
&:focus {
color: $white;
}
}
.btn-secondary {
@extend .btn;
background-color: $mediumLight;
border-color: $medium;
border-color: $mediumLight;
color: $dark;
}
.btn-alert {
@extend .btn;
background-color: $error;
border-color: $error;
color: $white;
&:hover {
background-color: $dark;
color: $light;
}
}

View File

@@ -4,11 +4,13 @@ label {
display: block;
}
textarea,
input {
width: 100%;
border: 1px solid $light;
background-color: $greyLight;
font-size: 1.6rem;
font-family: $fontMain;
padding: 0.5rem;
border-radius: 0.5rem;
@@ -21,3 +23,12 @@ input {
input[type="checkbox"] {
width: unset;
}
a {
color: $mediumDark;
&:focus,
&:visited {
color: $dark;
}
}

View File

@@ -8,3 +8,7 @@ main {
width: 100%;
height: calc(100vh - 4.5rem);
}
.main-top {
}

30
resources/sass/pages/memos.scss vendored Normal file
View File

@@ -0,0 +1,30 @@
.memo {
&-list {
h1 {
font-size: 2.2rem;
}
a {
text-decoration: none;
}
&:nth-child(even) {
background-color: $light;
}
&:hover {
background-color: $mediumLight;
.memo-date {
opacity: 1;
}
}
}
&-date {
opacity: 0.6;
}
}

View File

@@ -1,12 +1,14 @@
// Fonts
@import url('https://fonts.googleapis.com/css?family=Open+Sans');
$fontMain: 'Open Sans', sans-serif;
html {
font-size: 62.5%;
}
body {
font-family: 'Open Sans', sans-serif;
font-family: $fontMain;
font-size: 1.6rem;
color: $font;
}

View File

@@ -30,6 +30,7 @@ $base: 1rem;
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.flex-end {

View File

@@ -12,10 +12,6 @@
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>

View File

@@ -13,21 +13,12 @@ class MemosTest extends TestCase
{
use RefreshDatabase;
protected $user;
protected function setUp(): void
{
parent::setUp();
$this->actingAs($user = factory(User::class)->create(), 'api');
// $this->user = factory(User::class)->create();
}
/** @test */
public function an_unauthenticated_user_should_redirect_to_login()
{
$response = $this->post('/api/memos', $this->data());
$this->assertGuest($guard = null);
$response->assertRedirect('/login');
$this->assertCount(0, Memo::all());
}
@@ -61,6 +52,7 @@ class MemosTest extends TestCase
/** @test */
public function memo_name_are_required()
{
$this->actingAs($user = factory(\App\User::class)->create(), 'api');
$response = $this->post('/api/memos', array_merge($this->data(), ['name' => '']));
$response->assertSessionHasErrors('name');
@@ -70,6 +62,7 @@ class MemosTest extends TestCase
/** @test */
public function memo_are_required()
{
$this->actingAs($user = factory(\App\User::class)->create(), 'api');
$response = $this->post('/api/memos', array_merge($this->data(), ['memo' => '']));
$response->assertSessionHasErrors('memo');
@@ -122,6 +115,7 @@ class MemosTest extends TestCase
/** @test */
public function only_the_owner_can_patch_the_memo()
{
$this->actingAs($user = factory(User::class)->create(), 'api');
$memo = factory(Memo::class)->create();
$anotherUser = factory(User::class)->create();
@@ -150,6 +144,7 @@ class MemosTest extends TestCase
/** @test */
public function only_the_owner_can_delete_the_memo()
{
$this->actingAs($user = factory(User::class)->create(), 'api');
$memo = factory(Memo::class)->create();
$anotherUser = factory(User::class)->create();
@@ -162,7 +157,7 @@ class MemosTest extends TestCase
/** @test */
public function a_list_of_memos_can_be_fetched_for_the_authenticated_user()
{
$user = factory(User::class)->create();
$this->actingAs($user = factory(User::class)->create(), 'api');
$anotherUser = factory(User::class)->create();
$memo = factory(Memo::class)->create(['user_id' => $user->id]);
@@ -185,18 +180,16 @@ class MemosTest extends TestCase
/** @test */
public function only_the_users_memos_can_be_retrieved()
{
$this->actingAs($user = factory(User::class)->create(), 'api');
$user = factory(User::class)->create();
$memo = factory(Memo::class)->create(['user_id' => $user->id]);
$anotherUser = factory(User::class)->create();
$this->actingAs($anotherUser = factory(User::class)->create(), 'api');
$response = $this->get('/api/memos/' . $memo->id );
$response->assertStatus(Response::HTTP_FORBIDDEN);
}
/** @test */
// /** @test */
// public function user_can_add_tag_to_memo()
// {
// //$this->withoutExceptionHandling();
@@ -224,7 +217,7 @@ class MemosTest extends TestCase
// );
// }
/** @test */
// /** @test */
// public function user_can_remove_tag_to_memo()
// {
// //$this->withoutExceptionHandling();

View File

@@ -59,4 +59,15 @@ class UserAuthTest extends TestCase
$this->assertCount(1, User::all());
}
/** @test */
public function an_admin_can_fetch_all_users()
{
$this->actingAs($user = factory(User::class)->create(['role' => 2]), 'api');
$anotherUser = factory(User::class)->create();
$response = $this->get('/api/users')->assertStatus(Response::HTTP_OK);
$this->assertCount(2, User::all());
}
}

18
webpack.mix.js vendored
View File

@@ -1,15 +1,13 @@
const mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
require('laravel-mix-eslint');
mix.js('resources/js/app.js', 'public/js')
// .eslint({
// enforce: 'pre',
// test: /\.(js|vue)$/,
// exclude: /node_modules/,
// loader: 'eslint-loader',
// options: {}
// })
.sass('resources/sass/app.scss', 'public/css');