@@ -0,0 +1,81 @@ | |||
<style scoped> | |||
pre { | |||
font-size: .7em; | |||
} | |||
</style> | |||
<template> | |||
<div id="help"> | |||
<div class="col-lg-4 col-md-6"> | |||
<div class="card"> | |||
<div class="card-header"> | |||
<i class="icon ion-ios-heart"></i> Welcome on LessPass App | |||
</div> | |||
<div class="card-block"> | |||
<p class="card-text"> | |||
LessPass App helps you to store information about site with specials information like | |||
you bank account. LessPass store information it needs to regenerate a password. | |||
Here an example of the information we store on our database : | |||
<pre>{{ demoEntry | json 4 }}</pre> | |||
<p class="card-text"> | |||
No master password or generated password here. | |||
To regenerate a password, LessPass ask your master password, an your browser rebuild the | |||
password. | |||
</p> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="col-lg-4 col-md-6"> | |||
<div class="card"> | |||
<div class="card-header"> | |||
<i class="icon ion-ios-star"></i> Tips | |||
</div> | |||
<div class="card-block"> | |||
<ul> | |||
<li> | |||
Keep your master password in your head. | |||
</li> | |||
<li> | |||
Do not register on a site with your master password. | |||
</li> | |||
<li> | |||
Use full qualified domain name for your site<br>https://mail.google.com -> | |||
google.com<br> | |||
The LessPass web extension pre-fill site field for you with this mecanism. | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import Auth from '../services/auth'; | |||
export default { | |||
data() { | |||
return { | |||
demoEntry: { | |||
"id": "0cf8a11b-2375-4f3c-ad15-3194a64bb516", | |||
"site": "lesspass.com", | |||
"login": "demo@lesspass.com", | |||
"password": { | |||
"counter": 1, | |||
"settings": [ | |||
"lowercase", | |||
"uppercase", | |||
"numbers", | |||
"symbols" | |||
], | |||
"length": 12 | |||
}, | |||
"created": "2016-06-04T10:32:43.872900", | |||
"modified": "2016-06-04T10:32:43.872920" | |||
} | |||
}; | |||
}, | |||
ready(){ | |||
Auth.getUser().then(user=> { | |||
this.demoEntry.login = user.email; | |||
}); | |||
} | |||
}; | |||
</script> |
@@ -65,6 +65,12 @@ | |||
<span>passwords</span> | |||
</a> | |||
</li> | |||
<li class="sidebar-item" v-link-active> | |||
<a v-link="{ path: '/help/', activeClass: 'active' }" class="sidebar-link"> | |||
<i class="icon ion-ios-help-circle-outline"></i> | |||
<span>help</span> | |||
</a> | |||
</li> | |||
</ul> | |||
<ul class="sidebar-menu settings"> | |||
<li class="sidebar-item" v-link-active> | |||
@@ -161,4 +161,12 @@ locales.fr.login = { | |||
registrationInvalid: 'Vos informations de connection sont invalides' | |||
}; | |||
locales.en.entries = { | |||
search: 'search password information' | |||
}; | |||
locales.fr.entries = { | |||
search: 'recherchez' | |||
}; | |||
export default locales; |
@@ -5,6 +5,7 @@ import VueI18n from 'vue-i18n'; | |||
import App from './app'; | |||
import LandingPage from './pages/index'; | |||
import LoginPage from './pages/login'; | |||
import HelpPage from './pages/help'; | |||
import RegisterPage from './pages/register'; | |||
import SettingsPage from './pages/settings'; | |||
import EntriesPage from './pages/entries'; | |||
@@ -30,6 +31,9 @@ router.map({ | |||
'/login/': { | |||
component: LoginPage | |||
}, | |||
'/help/': { | |||
component: HelpPage | |||
}, | |||
'/register/': { | |||
component: RegisterPage | |||
}, | |||
@@ -1,12 +1,17 @@ | |||
<style scoped> | |||
pre { | |||
font-size: .7em; | |||
} | |||
</style> | |||
<template> | |||
<div id="passwords-page"> | |||
<div class="row m-y-1"> | |||
<div id="entries-page"> | |||
<div class="row"> | |||
<div class="col-md-6"> | |||
<div id="searchEntries"> | |||
<div class="input-group"> | |||
<span class="input-group-addon" id="search-addon"> | |||
<i class="icon ion-ios-search"></i> | |||
</span> | |||
<span class="input-group-addon" id="search-addon"> | |||
<i class="icon ion-ios-search"></i> | |||
</span> | |||
<input type="text" class="form-control" placeholder="{{{ $t.('entries.search') }}}" | |||
v-model="search" aria-describedby="search-addon" | |||
@keyup="filterEntry(search) | debounce 500"> | |||
@@ -17,7 +22,24 @@ | |||
<new-entry-button></new-entry-button> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="row m-y-2" v-show="!entries.length"> | |||
<help-component></help-component> | |||
<div class="col-lg-4 col-md-6"> | |||
<div class="card"> | |||
<div class="card-header"> | |||
<i class="icon ion-ios-key"></i> Let's go | |||
</div> | |||
<div class="card-block"> | |||
<p class="card-text"> | |||
Create your first entry by clicking the button | |||
<new-entry-button></new-entry-button> | |||
</p> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="row m-y-2"> | |||
<div class="col-lg-12"> | |||
<div class="card-columns"> | |||
<div v-for="entry in entries"> | |||
@@ -26,27 +48,6 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<div class="row m-t-1"> | |||
<div class="paginate"> | |||
<div class="col-xs-4 text-xs-left"> | |||
<button class="btn btn-primary btn-sm" v-if="count > limit" | |||
:disabled="(currentPage*limit >= count)" | |||
@click="getPreviousEntries()"> | |||
previous | |||
</button> | |||
</div> | |||
<div class="col-xs-4 text-xs-center" v-if="numberPages > 1"> | |||
{{ currentPage }} / {{ numberPages }} | |||
</div> | |||
<div class="col-xs-4 text-xs-right"> | |||
<button class="btn btn-primary btn-sm" v-if="count > limit" | |||
:disabled="(currentPage===1)" | |||
@click="getNextEntries()"> | |||
next | |||
</button> | |||
</div> | |||
</div> | |||
</div> | |||
<copy-password-modal :entry="selectedEntry"></copy-password-modal> | |||
</div> | |||
</template> | |||
@@ -55,6 +56,7 @@ | |||
import Entries from '../services/entries'; | |||
import LesspassEntry from '../components/entry.vue'; | |||
import NewEntryButton from '../components/new-entry-button'; | |||
import HelpComponent from '../components/help'; | |||
import CopyPasswordModal from '../components/copy-password-modal'; | |||
Entries.localStorage = localStorage; | |||
export default { | |||
@@ -72,6 +74,7 @@ | |||
}, | |||
components: { | |||
LesspassEntry, | |||
HelpComponent, | |||
NewEntryButton, | |||
CopyPasswordModal | |||
}, | |||
@@ -0,0 +1,20 @@ | |||
<style scoped> | |||
pre { | |||
font-size: .7em; | |||
} | |||
</style> | |||
<template> | |||
<div id="help-page"> | |||
<div class="row m-y-1"> | |||
<help-component></help-component> | |||
</div> | |||
</div> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import HelpComponent from '../components/help'; | |||
export default { | |||
components: { | |||
HelpComponent | |||
} | |||
}; | |||
</script> |
@@ -19,6 +19,13 @@ export default { | |||
return response.data; | |||
}); | |||
}, | |||
getUser() { | |||
const config = this.getRequestConfig(); | |||
return axios.get('/api/auth/me/', config).then(response => { | |||
Object.assign(this.user, response.data); | |||
return this.user; | |||
}); | |||
}, | |||
register(user) { | |||
return axios.post('/api/auth/register/', user).then(response => { | |||
return response.data; | |||
@@ -8,7 +8,7 @@ const localStorage = new LocalStorage('./localStorage'); | |||
auth.localStorage = localStorage; | |||
const user = { | |||
email: 'test@hwm.com', | |||
email: 'test@lesspass.com', | |||
password: 'password' | |||
}; | |||
const token = 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9'; | |||
@@ -28,6 +28,13 @@ test('should throw error if bad request', t => { | |||
}); | |||
}); | |||
test('should get user info', t => { | |||
nock('http://localhost/').get('/api/auth/me/').reply(200, {email: user.email}); | |||
return auth.getUser().then(u => { | |||
t.is(u.email, user.email); | |||
}); | |||
}); | |||
test('should register a user', t => { | |||
nock('http://localhost/').post('/api/auth/register/', user).reply(201, {email: user.email, pk: 1}); | |||
return auth.register(user).then(r => { | |||