Merge branch 'tailwindcss'
This commit is contained in:
commit
3b80ae051d
@ -13,7 +13,7 @@ server:
|
|||||||
jwt:
|
jwt:
|
||||||
auth:
|
auth:
|
||||||
secret: ${JWT_SECRET:-uiqzRNiMYwbObn/Ps5xTasYVeu/63ZuI+1oB98Ez+lY=} # Can be generated with `openssl rand -base64 32`
|
secret: ${JWT_SECRET:-uiqzRNiMYwbObn/Ps5xTasYVeu/63ZuI+1oB98Ez+lY=} # Can be generated with `openssl rand -base64 32`
|
||||||
validity: 1
|
validity: 24
|
||||||
unit: HOURS
|
unit: HOURS
|
||||||
refresh:
|
refresh:
|
||||||
secret: ${JWT_REFRESH_SECRET=-wWchkx44YGig4Q5Z7b7+E/3ymGEGd6PS7UGedMul3bg=} # Can be generated with `openssl rand -base64 32`
|
secret: ${JWT_REFRESH_SECRET=-wWchkx44YGig4Q5Z7b7+E/3ymGEGd6PS7UGedMul3bg=} # Can be generated with `openssl rand -base64 32`
|
||||||
|
|||||||
18
frontend/Caddyfile
Normal file
18
frontend/Caddyfile
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
:7000
|
||||||
|
|
||||||
|
log {
|
||||||
|
format single_field common_log
|
||||||
|
}
|
||||||
|
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
root * dist
|
||||||
|
file_server
|
||||||
|
|
||||||
|
try_files {path} /200.html
|
||||||
|
|
||||||
|
@nuxt {
|
||||||
|
path /_nuxt/*
|
||||||
|
}
|
||||||
|
|
||||||
|
header @nuxt Cache-Control "public,max-age=31536000,immutable" # 1 year
|
||||||
3
frontend/assets/css/tailwind.css
Normal file
3
frontend/assets/css/tailwind.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@import 'tailwindcss/base';
|
||||||
|
@import 'tailwindcss/components';
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
Binary file not shown.
@ -1,14 +1,3 @@
|
|||||||
/* roboto-regular - latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: local('Roboto'), local('Roboto-Regular'),
|
|
||||||
url('./fonts/roboto-v20-latin-regular.woff2') format('woff2')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.page-enter-active, .page-leave-active {
|
.page-enter-active, .page-leave-active {
|
||||||
transition: opacity .2s;
|
transition: opacity .2s;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card flat>
|
|
||||||
<v-form ref="form" v-model="valid">
|
|
||||||
<v-card-text>
|
|
||||||
<v-text-field
|
|
||||||
v-model="form.username"
|
|
||||||
:rules="usernameRules"
|
|
||||||
label="Username"
|
|
||||||
required
|
|
||||||
:prepend-icon="mdiAccount"
|
|
||||||
></v-text-field>
|
|
||||||
|
|
||||||
<v-text-field
|
|
||||||
v-model="form.password"
|
|
||||||
:rules="passwordRules"
|
|
||||||
label="Password"
|
|
||||||
required
|
|
||||||
:prepend-icon="mdiLock"
|
|
||||||
type="password"
|
|
||||||
></v-text-field>
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn
|
|
||||||
:disabled="!valid"
|
|
||||||
color="success"
|
|
||||||
type="submit"
|
|
||||||
@click.prevent="userLogin"
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-form>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mdiLock, mdiAccount } from '@mdi/js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'LoginForm',
|
|
||||||
data: () => ({
|
|
||||||
mdiLock,
|
|
||||||
mdiAccount,
|
|
||||||
valid: false,
|
|
||||||
form: {
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
},
|
|
||||||
usernameRules: [(v) => !!v || 'Name is required'],
|
|
||||||
passwordRules: [(v) => !!v || 'Password is required'],
|
|
||||||
}),
|
|
||||||
methods: {
|
|
||||||
userLogin() {
|
|
||||||
this.$auth
|
|
||||||
.loginWith('local', {
|
|
||||||
data: this.form,
|
|
||||||
})
|
|
||||||
.then((_) =>
|
|
||||||
this.$root.$emit('toast', 'Welcome back', 'success')
|
|
||||||
)
|
|
||||||
.catch((_) =>
|
|
||||||
this.$root.$emit('toast', 'Invalid credentials', 'error')
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<v-navigation-drawer v-model="drawer" clipped fixed app color="primary">
|
|
||||||
<client-only>
|
|
||||||
<v-list-item v-if="loggedIn">
|
|
||||||
<v-list-item-avatar>
|
|
||||||
<v-icon>{{ mdiAccount }}</v-icon>
|
|
||||||
</v-list-item-avatar>
|
|
||||||
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>
|
|
||||||
{{ $auth.$state.user.username }}
|
|
||||||
</v-list-item-title>
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
<v-divider />
|
|
||||||
<v-list>
|
|
||||||
<v-list-item
|
|
||||||
v-for="(item, i) in items"
|
|
||||||
:key="i"
|
|
||||||
:to="item.to"
|
|
||||||
router
|
|
||||||
nuxt
|
|
||||||
exact
|
|
||||||
>
|
|
||||||
<v-list-item-action>
|
|
||||||
<v-icon>{{ item.icon }}</v-icon>
|
|
||||||
</v-list-item-action>
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title v-text="item.title" />
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</client-only>
|
|
||||||
<template v-slot:append>
|
|
||||||
<client-only>
|
|
||||||
<div class="pa-2">
|
|
||||||
<v-btn
|
|
||||||
v-if="loggedIn"
|
|
||||||
block
|
|
||||||
color="secondary"
|
|
||||||
@click.stop="logout"
|
|
||||||
>
|
|
||||||
Logout
|
|
||||||
</v-btn>
|
|
||||||
<v-btn v-else block color="secondary" to="/account">
|
|
||||||
Login
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
</client-only>
|
|
||||||
</template>
|
|
||||||
</v-navigation-drawer>
|
|
||||||
|
|
||||||
<v-app-bar clipped-left fixed app color="primary">
|
|
||||||
<v-app-bar-nav-icon @click.stop="drawer = !drawer">
|
|
||||||
<v-icon>{{ mdiMenu }}</v-icon>
|
|
||||||
</v-app-bar-nav-icon>
|
|
||||||
<v-btn to="/" text rounded>Simple Notes</v-btn>
|
|
||||||
<v-spacer />
|
|
||||||
<client-only>
|
|
||||||
<v-btn aria-label="theme switcher" icon @click="toggleTheme">
|
|
||||||
<v-icon>
|
|
||||||
{{
|
|
||||||
$vuetify.theme.dark
|
|
||||||
? mdiBrightness2
|
|
||||||
: mdiBrightness5
|
|
||||||
}}
|
|
||||||
</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</client-only>
|
|
||||||
</v-app-bar>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
mdiMenu,
|
|
||||||
mdiBrightness5,
|
|
||||||
mdiBrightness2,
|
|
||||||
mdiPencil,
|
|
||||||
mdiHome,
|
|
||||||
mdiAccount,
|
|
||||||
} from '@mdi/js'
|
|
||||||
import { mapState } from 'vuex'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Navbar',
|
|
||||||
data: () => ({
|
|
||||||
mdiMenu,
|
|
||||||
mdiBrightness5,
|
|
||||||
mdiBrightness2,
|
|
||||||
mdiAccount,
|
|
||||||
drawer: false,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
icon: mdiHome,
|
|
||||||
title: 'Welcome',
|
|
||||||
to: '/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: mdiPencil,
|
|
||||||
title: 'Notes',
|
|
||||||
to: '/notes',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
computed: {
|
|
||||||
...mapState('auth', ['loggedIn']),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async logout() {
|
|
||||||
this.$store.commit('notes/clear')
|
|
||||||
await this.$auth.logout()
|
|
||||||
},
|
|
||||||
toggleTheme() {
|
|
||||||
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
|
|
||||||
const theme = this.$vuetify.theme.dark ? 'dark' : 'light'
|
|
||||||
localStorage.setItem('theme', theme)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="d-flex flex-column" height="100%">
|
|
||||||
<v-card-title>
|
|
||||||
<h2 class="title primary--text">{{ title }}</h2>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text> Last updated {{ prettyDate }} </v-card-text>
|
|
||||||
<v-card-actions>
|
|
||||||
<TagsGroup :tags="tags" />
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn text color="accent" @click="$router.push(`/notes/${uuid}`)"
|
|
||||||
>View</v-btn
|
|
||||||
>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { format } from 'timeago.js'
|
|
||||||
import TagsGroup from '@/components/TagsGroup'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'NoteCard',
|
|
||||||
components: { TagsGroup },
|
|
||||||
props: {
|
|
||||||
uuid: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
prettyDate() {
|
|
||||||
return format(this.updatedAt)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card flat>
|
|
||||||
<v-form ref="form" v-model="valid">
|
|
||||||
<v-card-text>
|
|
||||||
<v-text-field
|
|
||||||
v-model="form.username"
|
|
||||||
:rules="usernameRules"
|
|
||||||
label="Username"
|
|
||||||
required
|
|
||||||
:prepend-icon="mdiAccount"
|
|
||||||
></v-text-field>
|
|
||||||
|
|
||||||
<v-text-field
|
|
||||||
v-model="form.password"
|
|
||||||
:rules="passwordRules"
|
|
||||||
label="Password"
|
|
||||||
required
|
|
||||||
:prepend-icon="mdiLock"
|
|
||||||
type="password"
|
|
||||||
></v-text-field>
|
|
||||||
|
|
||||||
<v-text-field
|
|
||||||
v-model="confirm"
|
|
||||||
:rules="confirmRules"
|
|
||||||
label="Confirm your password"
|
|
||||||
required
|
|
||||||
:prepend-icon="mdiLock"
|
|
||||||
type="password"
|
|
||||||
></v-text-field>
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn
|
|
||||||
:disabled="!valid"
|
|
||||||
color="success"
|
|
||||||
type="submit"
|
|
||||||
@click.prevent="registerUser"
|
|
||||||
>
|
|
||||||
Register
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-form>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mdiAccount, mdiAt, mdiLock } from '@mdi/js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'RegisterForm',
|
|
||||||
data: (vm) => ({
|
|
||||||
mdiLock,
|
|
||||||
mdiAccount,
|
|
||||||
mdiAt,
|
|
||||||
valid: false,
|
|
||||||
form: {
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
},
|
|
||||||
usernameRules: [
|
|
||||||
(v) => !!v || 'Username is required',
|
|
||||||
(v) =>
|
|
||||||
v.length <= 50 ||
|
|
||||||
'Username must be shorter or equal to 50 characters',
|
|
||||||
(v) =>
|
|
||||||
v.length >= 3 ||
|
|
||||||
'Username must be longer or equal to 3 characters',
|
|
||||||
],
|
|
||||||
passwordRules: [
|
|
||||||
(v) => !!v || 'Password is required',
|
|
||||||
(v) =>
|
|
||||||
v.length >= 6 ||
|
|
||||||
'Password must be longer or equal to 6 characters',
|
|
||||||
],
|
|
||||||
confirm: '',
|
|
||||||
confirmRules: [
|
|
||||||
(v) => !!v || 'Password is required',
|
|
||||||
(v) => v === vm.form.password || 'Both passwords must be equals',
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
methods: {
|
|
||||||
registerUser() {
|
|
||||||
this.$axios
|
|
||||||
.post('/user', this.form)
|
|
||||||
.then(() => this.$root.$emit('register::success'))
|
|
||||||
.catch(() =>
|
|
||||||
this.$root.$emit(
|
|
||||||
'toast',
|
|
||||||
'Please choose another username',
|
|
||||||
'error'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="tags">
|
|
||||||
<v-chip
|
|
||||||
v-for="(tag, index) in tags"
|
|
||||||
:key="index"
|
|
||||||
active
|
|
||||||
color="secondary"
|
|
||||||
>
|
|
||||||
{{ tag }}
|
|
||||||
</v-chip>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'TagsGroup',
|
|
||||||
props: {
|
|
||||||
tags: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
.tags {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags .v-chip {
|
|
||||||
margin: 4px 8px 4px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,41 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app dark>
|
<div class="h-screen font-sans text-gray-100">
|
||||||
<Navbar />
|
<nuxt />
|
||||||
<v-main>
|
</div>
|
||||||
<v-container fill-height>
|
|
||||||
<v-row no-gutters justify="center">
|
|
||||||
<nuxt />
|
|
||||||
</v-row>
|
|
||||||
<v-snackbar v-model="toast" :color="color">{{
|
|
||||||
msg
|
|
||||||
}}</v-snackbar>
|
|
||||||
</v-container>
|
|
||||||
</v-main>
|
|
||||||
</v-app>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import Navbar from '@/components/Navbar'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { Navbar },
|
|
||||||
data: () => ({
|
|
||||||
toast: false,
|
|
||||||
color: '',
|
|
||||||
msg: '',
|
|
||||||
}),
|
|
||||||
mounted() {
|
|
||||||
this.$root.$on('toast', (msg, color) => {
|
|
||||||
this.msg = msg
|
|
||||||
this.color = color || ''
|
|
||||||
this.toast = true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
overflow-y: auto;
|
@apply bg-gray-900;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,13 +1,3 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app>
|
<nuxt />
|
||||||
<v-main>
|
|
||||||
<nuxt />
|
|
||||||
</v-main>
|
|
||||||
</v-app>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
|
||||||
html {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app dark>
|
<div
|
||||||
<h1 v-if="error.statusCode === 404">
|
class="h-screen container mx-auto h-full flex justify-center items-center"
|
||||||
{{ pageNotFound }}
|
>
|
||||||
</h1>
|
<div
|
||||||
<h1 v-else>
|
class="text-gray-200 w-full md:w-1/2 lg:w-1/3 bg-gray-800 border-teal-500 p-8 border-t-8 bg-white mb-6 rounded-lg shadow-lg"
|
||||||
{{ otherError }}
|
>
|
||||||
</h1>
|
<h1 v-if="error.statusCode === 404">
|
||||||
<NuxtLink to="/">
|
{{ pageNotFound }}
|
||||||
Home page
|
</h1>
|
||||||
</NuxtLink>
|
<h1 v-else>
|
||||||
</v-app>
|
{{ otherError }}
|
||||||
|
</h1>
|
||||||
|
<NuxtLink to="/" class="text-blue-200 underline">
|
||||||
|
Home page
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -34,14 +34,14 @@ export default ({ command }) => ({
|
|||||||
/*
|
/*
|
||||||
** Plugins to load before mounting the App
|
** Plugins to load before mounting the App
|
||||||
*/
|
*/
|
||||||
plugins: ['~/plugins/theme.client.js'],
|
plugins: [],
|
||||||
/*
|
/*
|
||||||
** Nuxt.js dev-modules
|
** Nuxt.js dev-modules
|
||||||
*/
|
*/
|
||||||
buildModules: [
|
buildModules: [
|
||||||
// Doc: https://github.com/nuxt-community/eslint-module
|
// Doc: https://github.com/nuxt-community/eslint-module
|
||||||
'@nuxtjs/vuetify',
|
|
||||||
'@nuxtjs/eslint-module',
|
'@nuxtjs/eslint-module',
|
||||||
|
'@nuxtjs/tailwindcss',
|
||||||
],
|
],
|
||||||
/*
|
/*
|
||||||
** Nuxt.js modules
|
** Nuxt.js modules
|
||||||
@ -66,7 +66,7 @@ export default ({ command }) => ({
|
|||||||
|
|
||||||
auth: {
|
auth: {
|
||||||
redirect: {
|
redirect: {
|
||||||
login: '/account',
|
login: '/signin',
|
||||||
logout: '/',
|
logout: '/',
|
||||||
home: '/notes',
|
home: '/notes',
|
||||||
},
|
},
|
||||||
@ -98,45 +98,11 @@ export default ({ command }) => ({
|
|||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
|
||||||
** vuetify module configuration
|
|
||||||
** https://github.com/nuxt-community/vuetify-module
|
|
||||||
*/
|
|
||||||
vuetify: {
|
|
||||||
defaultAssets: false,
|
|
||||||
theme: {
|
|
||||||
dark: false,
|
|
||||||
themes: {
|
|
||||||
dark: {
|
|
||||||
primary: '#21CFF3',
|
|
||||||
secondary: '#FF4081',
|
|
||||||
accent: '#ffe18d',
|
|
||||||
success: '#4CAF50',
|
|
||||||
info: '#2196F3',
|
|
||||||
warning: '#FB8C00',
|
|
||||||
error: '#FF5252',
|
|
||||||
},
|
|
||||||
light: {
|
|
||||||
primary: '#1976D2',
|
|
||||||
secondary: '#e91e63',
|
|
||||||
accent: '#30b1dc',
|
|
||||||
success: '#4CAF50',
|
|
||||||
info: '#2196F3',
|
|
||||||
warning: '#FB8C00',
|
|
||||||
error: '#FF5252',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
generate: {
|
|
||||||
fallback: '404.html',
|
|
||||||
},
|
|
||||||
/*
|
/*
|
||||||
** Build configuration
|
** Build configuration
|
||||||
*/
|
*/
|
||||||
build: {
|
build: {
|
||||||
extractCSS: true,
|
extractCSS: false,
|
||||||
|
|
||||||
terser: {
|
terser: {
|
||||||
parallel: true,
|
parallel: true,
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
"@mdi/js": "^5.1.45",
|
"@mdi/js": "^5.1.45",
|
||||||
"@nuxtjs/eslint-config": "^2.0.2",
|
"@nuxtjs/eslint-config": "^2.0.2",
|
||||||
"@nuxtjs/eslint-module": "^1.1.0",
|
"@nuxtjs/eslint-module": "^1.1.0",
|
||||||
"@nuxtjs/vuetify": "^1.0.0",
|
"@nuxtjs/tailwindcss": "^2.0.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-col cols="12" sm="8" md="6" xl="4">
|
|
||||||
<v-card color="secondary">
|
|
||||||
<v-btn icon to="/" aria-label="Go back">
|
|
||||||
<v-icon color="white">{{ mdiArrowLeft }}</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-card-title class="text-center justify-center py-6">
|
|
||||||
<h1 class="font-weight-bold display-2 white--text">
|
|
||||||
Account
|
|
||||||
</h1>
|
|
||||||
</v-card-title>
|
|
||||||
|
|
||||||
<v-tabs v-model="tab" grow color="accent">
|
|
||||||
<v-tab v-for="tab in tabs" :key="tab">
|
|
||||||
{{ tab }}
|
|
||||||
</v-tab>
|
|
||||||
</v-tabs>
|
|
||||||
|
|
||||||
<v-tabs-items v-model="tab">
|
|
||||||
<v-tab-item v-for="tab in tabs" :key="tab">
|
|
||||||
<v-card flat>
|
|
||||||
<v-card-text>
|
|
||||||
<LoginForm v-if="tab === 'Login'"></LoginForm>
|
|
||||||
<RegisterForm v-else></RegisterForm>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-tab-item>
|
|
||||||
</v-tabs-items>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mdiArrowLeft } from '@mdi/js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
options: {
|
|
||||||
auth: 'guest',
|
|
||||||
},
|
|
||||||
data: () => ({
|
|
||||||
mdiArrowLeft,
|
|
||||||
tab: 0,
|
|
||||||
tabs: ['Login', 'Register'],
|
|
||||||
}),
|
|
||||||
mounted() {
|
|
||||||
this.$root.$on('register::success', () => {
|
|
||||||
this.$root.$emit('toast', 'Welcome', 'success')
|
|
||||||
this.tab = 0
|
|
||||||
})
|
|
||||||
},
|
|
||||||
head: () => ({
|
|
||||||
title: 'Account',
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-col cols="12">
|
|
||||||
<v-card color="secondary">
|
|
||||||
<v-card-title class="text-center justify-center py-6">
|
|
||||||
<h1 class="font-weight-bold headline white--text">
|
|
||||||
Create a note
|
|
||||||
</h1>
|
|
||||||
<v-spacer />
|
|
||||||
<v-switch v-model="preview"></v-switch>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card flat>
|
|
||||||
<v-card-text>
|
|
||||||
<v-textarea
|
|
||||||
v-if="!preview"
|
|
||||||
v-model="text"
|
|
||||||
autocapitalize="off"
|
|
||||||
autocomplete="off"
|
|
||||||
spellcheck="false"
|
|
||||||
autocorrect="off"
|
|
||||||
label="Text"
|
|
||||||
auto-grow
|
|
||||||
></v-textarea>
|
|
||||||
<client-only v-else>
|
|
||||||
<!-- html is sanitized -->
|
|
||||||
<!-- eslint-disable-next-line -->
|
|
||||||
<div class="html" v-html="html" />
|
|
||||||
</client-only>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import markdown from '~/utils/markdown'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Create',
|
|
||||||
data: () => ({
|
|
||||||
delimiters: [' ', ','],
|
|
||||||
form: {
|
|
||||||
title: '',
|
|
||||||
tags: [],
|
|
||||||
},
|
|
||||||
text: '',
|
|
||||||
possibleTags: [],
|
|
||||||
conflict: false,
|
|
||||||
preview: false,
|
|
||||||
}),
|
|
||||||
computed: {
|
|
||||||
html() {
|
|
||||||
return markdown(this.text)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$axios.get('/tags').then((e) => (this.possibleTags = e.data))
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
createNote() {
|
|
||||||
this.conflict = false
|
|
||||||
this.$axios
|
|
||||||
.post(`/notes/${this.form.title}`, {
|
|
||||||
tags: this.form.tags,
|
|
||||||
})
|
|
||||||
.then((_) => {
|
|
||||||
this.$router.push('/notes')
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
if (e.response && e.response.status === 409) {
|
|
||||||
this.conflict = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.html h1 {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,71 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-col cols="12">
|
<div>
|
||||||
<v-container fluid>
|
<nav>
|
||||||
<v-row>
|
<ul>
|
||||||
<v-col
|
<li
|
||||||
v-for="(item, index) in items"
|
v-for="link in links"
|
||||||
:key="index"
|
:key="link.name"
|
||||||
class="d-flex"
|
class="text-blue-200"
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
>
|
||||||
<v-card
|
<nuxt-link :to="link.url" class="underline">{{
|
||||||
:color="item.color"
|
link.name
|
||||||
dark
|
}}</nuxt-link>
|
||||||
style="min-height: 350px; width: 100%;"
|
</li>
|
||||||
>
|
<li class="text-blue-200">
|
||||||
<v-card-title class="headline">
|
<button class="underline" @click="logout">
|
||||||
{{ item.title }}
|
Sign Out
|
||||||
</v-card-title>
|
</button>
|
||||||
<v-card-subtitle>
|
</li>
|
||||||
{{ item.content }}
|
</ul>
|
||||||
</v-card-subtitle>
|
</nav>
|
||||||
<v-card-actions>
|
<client-only>
|
||||||
<v-btn text>See a demo</v-btn>
|
<div v-if="$auth.$state.loggedIn">
|
||||||
</v-card-actions>
|
User: {{ $auth.$state.user }}
|
||||||
</v-card>
|
</div>
|
||||||
</v-col>
|
</client-only>
|
||||||
</v-row>
|
</div>
|
||||||
</v-container>
|
|
||||||
</v-col>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
title: 'Home',
|
name: 'Home',
|
||||||
|
data: () => ({
|
||||||
|
links: [
|
||||||
|
{ url: '/signin', name: 'Sign In' },
|
||||||
|
{ url: '/register', name: 'Register' },
|
||||||
|
{ url: '/notes', name: 'Notes' },
|
||||||
|
{ url: '/404', name: '404' },
|
||||||
|
],
|
||||||
|
}),
|
||||||
options: {
|
options: {
|
||||||
auth: false,
|
auth: false,
|
||||||
},
|
},
|
||||||
data: () => ({
|
methods: {
|
||||||
items: [
|
async logout() {
|
||||||
{
|
this.$store.commit('notes/clear')
|
||||||
title: 'Writes your notes in markdown',
|
await this.$auth.logout()
|
||||||
content:
|
},
|
||||||
'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur aut consectetur debitis harum' +
|
},
|
||||||
' illum in incidunt maxime modi quae velit? Aspernatur, consequatur error' +
|
|
||||||
' mollitia nesciunt officiis porro ratione sequi voluptas?',
|
|
||||||
color: 'indigo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '',
|
|
||||||
content: '',
|
|
||||||
color: 'purple',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '',
|
|
||||||
content: '',
|
|
||||||
color: 'warning',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '',
|
|
||||||
content: '',
|
|
||||||
color: 'teal darken-2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
head: () => ({
|
|
||||||
title: 'Home',
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
&
|
|
||||||
|
|||||||
@ -1,39 +1,52 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card>
|
<div>
|
||||||
<v-card-title>
|
<div>{{ uuid }}</div>
|
||||||
{{ note.title }}
|
<div v-if="note">{{ note }}</div>
|
||||||
<v-spacer />
|
<!-- html is sanitized -->
|
||||||
<TagsGroup :tags="note.tags" />
|
<!-- eslint-disable-next-line -->
|
||||||
</v-card-title>
|
<div class="container mx-auto" id="note" v-html="html"></div>
|
||||||
<v-card-text>
|
</div>
|
||||||
<!-- html is sanitized -->
|
|
||||||
<!-- eslint-disable-next-line -->
|
|
||||||
<div v-html="renderedMarkdown"></div>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState, mapActions, mapGetters } from 'vuex'
|
||||||
import renderMarkdown from '@/utils/markdown'
|
import renderMarkdown from '@/utils/markdown'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Note',
|
name: 'Note',
|
||||||
data: () => ({
|
data: () => ({
|
||||||
note: {
|
note: {},
|
||||||
tags: [],
|
html: '',
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
renderedMarkdown() {
|
...mapState('notes', ['notes', 'isInitialized']),
|
||||||
return !this.note.content
|
...mapGetters('notes', ['find']),
|
||||||
? ''
|
uuid() {
|
||||||
: renderMarkdown(this.note.content).contents
|
return this.$route.params.uuid
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$axios
|
if (!this.isInitialized) {
|
||||||
.$get(`/notes/${this.$route.params.uuid}`)
|
this.load().then(() => {
|
||||||
.then((note) => (this.note = note))
|
this.note = this.find(this.uuid)
|
||||||
|
this.render()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.note = this.find(this.uuid)
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions('notes', ['load']),
|
||||||
|
render() {
|
||||||
|
this.html = renderMarkdown(this.note.content).contents
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#note h1 {
|
||||||
|
@apply text-5xl font-bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,46 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="container p-4 mx-auto bg-gray-800">
|
||||||
<v-progress-circular
|
<h1 class="text-center">My Notes</h1>
|
||||||
v-if="!isInitialized"
|
<nuxt-link
|
||||||
:size="100"
|
to="/notes/new"
|
||||||
:width="7"
|
class="bg-blue-700 hover:bg-blue-500 text-white font-bold py-2 px-4 rounded"
|
||||||
color="purple"
|
>New note</nuxt-link
|
||||||
indeterminate
|
>
|
||||||
></v-progress-circular>
|
<input
|
||||||
<v-card v-if="isEmpty">
|
id="search"
|
||||||
<v-card-text>No notes yet :/</v-card-text>
|
name="search"
|
||||||
</v-card>
|
class="block p-2 my-2 appearance-none w-full bg-transparent border-white-200 border-b focus:border-red-500"
|
||||||
<v-row v-else>
|
placeholder="search"
|
||||||
<v-col
|
aria-label="search"
|
||||||
v-for="note in notes"
|
/>
|
||||||
:key="note.uuid"
|
<div v-for="note in notes" :key="note.uuid">
|
||||||
cols="12"
|
<div class="flex justify-between items-center">
|
||||||
md="6"
|
<nuxt-link :to="'/notes/' + note.uuid" class="text-lg">{{
|
||||||
lg="4"
|
note.title
|
||||||
xl="3"
|
}}</nuxt-link>
|
||||||
>
|
<div class="py-2">
|
||||||
<NoteCard
|
<span
|
||||||
:uuid="note.uuid"
|
v-for="(tag, index) in note.tags"
|
||||||
:title="note.title"
|
:key="index"
|
||||||
:updated-at="note.updatedAt"
|
class="inline-block text-sm bg-gray-500 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 ml-2"
|
||||||
:tags="note.tags"
|
>{{ tag }}</span
|
||||||
/>
|
>
|
||||||
</v-col>
|
</div>
|
||||||
</v-row>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters, mapActions } from 'vuex'
|
import { mapActions, mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Notes',
|
name: 'Notes',
|
||||||
data: () => ({
|
|
||||||
loading: true,
|
|
||||||
}),
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState('notes', ['notes', 'isInitialized']),
|
...mapState('notes', ['notes', 'isInitialized']),
|
||||||
...mapGetters('notes', ['isEmpty']),
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (!this.isInitialized) this.load()
|
if (!this.isInitialized) this.load()
|
||||||
@ -48,8 +45,5 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
...mapActions('notes', ['load']),
|
...mapActions('notes', ['load']),
|
||||||
},
|
},
|
||||||
head: () => ({
|
|
||||||
title: 'My notes',
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
44
frontend/pages/notes/new.vue
Normal file
44
frontend/pages/notes/new.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<form @submit.prevent="submit">
|
||||||
|
<textarea
|
||||||
|
v-model="content"
|
||||||
|
aria-label="markdown input"
|
||||||
|
name="content"
|
||||||
|
rows="30"
|
||||||
|
style="outline: none !important;"
|
||||||
|
class="w-full bg-gray-800 p-5"
|
||||||
|
spellcheck="false"
|
||||||
|
></textarea>
|
||||||
|
<button
|
||||||
|
class="block bg-blue-700 hover:bg-blue-500 text-white font-bold py-2 px-4 rounded"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapActions } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'New',
|
||||||
|
data: () => ({
|
||||||
|
title: 'Test',
|
||||||
|
tags: [],
|
||||||
|
content: '',
|
||||||
|
}),
|
||||||
|
methods: {
|
||||||
|
...mapActions('notes', ['create']),
|
||||||
|
submit() {
|
||||||
|
this.create({
|
||||||
|
title: this.title,
|
||||||
|
tags: this.tags,
|
||||||
|
content: this.content,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
127
frontend/pages/register.vue
Normal file
127
frontend/pages/register.vue
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="h-screen container mx-auto h-full flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<div class="w-full md:w-1/2 lg:w-1/3">
|
||||||
|
<h1 class="font-semibold text-lg mb-6 text-center">
|
||||||
|
Create an Account
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
class="bg-gray-800 border-teal-500 p-8 border-t-8 bg-white mb-6 rounded-lg shadow-lg"
|
||||||
|
>
|
||||||
|
<form @submit.prevent="register">
|
||||||
|
<div
|
||||||
|
v-if="showError"
|
||||||
|
class="bg-red-500 border-l-4 border-red-700 text-red-200 p-4 mb-4"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<p class="font-bold">Error</p>
|
||||||
|
<p>{{ error }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label
|
||||||
|
for="username"
|
||||||
|
class="font-bold text-grey-darker block mb-2"
|
||||||
|
>
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
v-model="form.username"
|
||||||
|
name="username"
|
||||||
|
class="shadow focus:shadow-outline block appearance-none w-full bg-gray-700 border-gray-500 hover:border-gray-500 px-2 py-2 rounded shadow"
|
||||||
|
placeholder="Your Username"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label
|
||||||
|
for="password"
|
||||||
|
class="font-bold text-grey-darker block mb-2"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
v-model="form.password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
class="shadow focus:shadow-outline block appearance-none w-full bg-gray-700 border-gray-500 hover:border-gray-500 px-2 py-2 rounded shadow"
|
||||||
|
placeholder="Your Password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label
|
||||||
|
for="confirm"
|
||||||
|
class="font-bold text-grey-darker block mb-2"
|
||||||
|
>
|
||||||
|
Confirm your password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="confirm"
|
||||||
|
v-model="confirm"
|
||||||
|
name="confirm"
|
||||||
|
type="password"
|
||||||
|
class="shadow focus:shadow-outline block appearance-none w-full bg-gray-700 border-gray-500 hover:border-gray-500 px-2 py-2 rounded shadow"
|
||||||
|
placeholder="Confirm your Password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mt-6">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full bg-teal-500 hover:bg-teal-400 text-white font-bold py-2 px-4 rounded"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-gray-200 text-sm">
|
||||||
|
Already have an account?
|
||||||
|
<nuxt-link
|
||||||
|
to="/signin"
|
||||||
|
class="no-underline text-blue-500 font-bold"
|
||||||
|
>Sign in
|
||||||
|
</nuxt-link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SignIn',
|
||||||
|
options: {
|
||||||
|
auth: false, // FIXME: auth: 'guest'
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
confirm: '',
|
||||||
|
form: {
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
error: '',
|
||||||
|
showError: false,
|
||||||
|
}),
|
||||||
|
methods: {
|
||||||
|
register() {
|
||||||
|
this.$axios
|
||||||
|
.post('/user', this.form)
|
||||||
|
.then(() => this.$router.push('/signin'))
|
||||||
|
.catch((e) => {
|
||||||
|
if (e.response) {
|
||||||
|
this.error = e.response.data.error
|
||||||
|
this.showError = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
head: () => ({
|
||||||
|
title: 'Sign in',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
</script>
|
||||||
93
frontend/pages/signin.vue
Normal file
93
frontend/pages/signin.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="h-screen container mx-auto h-full flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<div class="w-full md:w-1/2 lg:w-1/3">
|
||||||
|
<h1 class="font-semibold text-lg mb-6 text-center">Sign In</h1>
|
||||||
|
<div
|
||||||
|
class="bg-gray-800 border-teal-500 p-8 border-t-8 bg-white mb-6 rounded-lg shadow-lg"
|
||||||
|
>
|
||||||
|
<form @submit.prevent="userLogin">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label
|
||||||
|
for="username"
|
||||||
|
class="font-bold text-grey-darker block mb-2"
|
||||||
|
>
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
v-model="form.username"
|
||||||
|
name="username"
|
||||||
|
class="shadow focus:shadow-outline block appearance-none w-full bg-gray-700 border-gray-500 hover:border-gray-500 px-2 py-2 rounded shadow"
|
||||||
|
placeholder="Your Username"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label
|
||||||
|
for="password"
|
||||||
|
class="font-bold text-grey-darker block mb-2"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
v-model="form.password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
class="shadow focus:shadow-outline block appearance-none w-full bg-gray-700 border-gray-500 hover:border-gray-500 px-2 py-2 rounded shadow"
|
||||||
|
placeholder="Your Password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mt-6">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full bg-teal-500 hover:bg-teal-400 text-white font-bold py-2 px-4 rounded"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-gray-200 text-sm">
|
||||||
|
Don't have an account?
|
||||||
|
<nuxt-link
|
||||||
|
to="/register"
|
||||||
|
class="no-underline text-blue-500 font-bold"
|
||||||
|
>Create an Account</nuxt-link
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SignIn',
|
||||||
|
options: {
|
||||||
|
auth: false, // FIXME: auth: 'guest'
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
form: {
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
methods: {
|
||||||
|
userLogin() {
|
||||||
|
this.$auth
|
||||||
|
.loginWith('local', {
|
||||||
|
data: this.form,
|
||||||
|
})
|
||||||
|
.then(() => this.$router.push('/'))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
head: () => ({
|
||||||
|
title: 'Sign in',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export default function ({ $vuetify }) {
|
|
||||||
const theme = localStorage.getItem('theme') ?? 'light'
|
|
||||||
$vuetify.theme.dark = theme === 'dark'
|
|
||||||
}
|
|
||||||
@ -24,9 +24,8 @@ export const mutations = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
async load({ commit }) {
|
load({ commit }) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
return this.$axios.get('/notes').then(({ data }) => {
|
||||||
this.$axios.get('/notes').then(({ data }) => {
|
|
||||||
commit('set', data)
|
commit('set', data)
|
||||||
commit('setInitialized')
|
commit('setInitialized')
|
||||||
})
|
})
|
||||||
@ -49,7 +48,6 @@ export const actions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
isEmpty(state) {
|
isEmpty: (state) => state.isInitialized && state.notes.length === 0,
|
||||||
return state.isInitialized && state.notes.length === 0
|
find: (state) => (uuid) => state.notes.find((note) => note.uuid === uuid),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
38
frontend/tailwind.config.js
Normal file
38
frontend/tailwind.config.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
** TailwindCSS Configuration File
|
||||||
|
**
|
||||||
|
** Docs: https://tailwindcss.com/docs/configuration
|
||||||
|
** Default: https://github.com/tailwindcss/tailwindcss/blob/master/stubs/defaultConfig.stub.js
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
gray: {
|
||||||
|
100: '#F8F9FA',
|
||||||
|
200: '#EBEBEB',
|
||||||
|
300: '#DEE2E6',
|
||||||
|
400: '#CED4DA',
|
||||||
|
500: '#ADB5BD',
|
||||||
|
600: '#888',
|
||||||
|
700: '#444',
|
||||||
|
800: '#303030',
|
||||||
|
900: '#222',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
variants: {},
|
||||||
|
plugins: [],
|
||||||
|
purge: {
|
||||||
|
// Learn more on https://tailwindcss.com/docs/controlling-file-size/#removing-unused-css
|
||||||
|
enabled: process.env.NODE_ENV === 'production',
|
||||||
|
content: [
|
||||||
|
'components/**/*.vue',
|
||||||
|
'layouts/**/*.vue',
|
||||||
|
'pages/**/*.vue',
|
||||||
|
'plugins/**/*.js',
|
||||||
|
'nuxt.config.js',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
1908
frontend/yarn.lock
1908
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user