merged master in
@ -6,5 +6,8 @@ npm-debug.log
|
||||
|
||||
Dockerfile
|
||||
docker-compose*.yml
|
||||
scripts/
|
||||
|
||||
.env
|
||||
|
||||
cypress/
|
||||
|
||||
4
.gitignore
vendored
@ -72,6 +72,10 @@ dist
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# TEMORIRY
|
||||
static/uploads
|
||||
|
||||
cypress/videos
|
||||
cypress/screenshots/
|
||||
|
||||
40
.travis.yml
@ -1,28 +1,42 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
services:
|
||||
- docker
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- node_modules
|
||||
services:
|
||||
- docker
|
||||
|
||||
env:
|
||||
- DOCKER_COMPOSE_VERSION=1.23.2
|
||||
|
||||
before_install:
|
||||
- sudo rm /usr/local/bin/docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- chmod +x docker-compose
|
||||
- sudo mv docker-compose /usr/local/bin
|
||||
|
||||
install:
|
||||
- docker build --build-arg BUILD_COMMIT=$TRAVIS_COMMIT -t humanconnection/nitro-web .
|
||||
- docker-compose -f docker-compose.yml up -d
|
||||
- git clone https://github.com/Human-Connection/Nitro-Backend.git ../Nitro-Backend
|
||||
- git -C "../Nitro-Backend" checkout $TRAVIS_BRANCH || echo "Branch \`$TRAVIS_BRANCH\` does not exist, falling back to \`master\`"
|
||||
- docker-compose -f ../Nitro-Backend/docker-compose.yml -f ../Nitro-Backend/docker-compose.travis.yml up -d
|
||||
- yarn global add cypress wait-on
|
||||
- yarn add cypress-cucumber-preprocessor
|
||||
|
||||
script:
|
||||
- docker run humanconnection/nitro-web yarn run lint
|
||||
- docker-compose exec webapp yarn run lint
|
||||
- docker-compose exec webapp yarn run test
|
||||
- docker-compose -f ../Nitro-Backend/docker-compose.yml exec backend yarn run db:seed > /dev/null
|
||||
- wait-on http://localhost:3000
|
||||
- cypress run --record --key $CYPRESS_TOKEN
|
||||
|
||||
after_success:
|
||||
# - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
|
||||
# - chmod +x send.sh
|
||||
# - ./send.sh success $WEBHOOK_URL
|
||||
- if [ $TRAVIS_BRANCH == "master" ] && [ $TRAVIS_EVENT_TYPE == "push" ]; then
|
||||
docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD";
|
||||
docker tag humanconnection/nitro-web humanconnection/nitro-web:latest;
|
||||
docker push humanconnection/nitro-web:latest;
|
||||
fi
|
||||
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
|
||||
- chmod +x send.sh
|
||||
- ./send.sh success $WEBHOOK_URL
|
||||
|
||||
after_failure:
|
||||
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
|
||||
@ -30,6 +44,10 @@ after_failure:
|
||||
- ./send.sh failure $WEBHOOK_URL
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
script: scripts/docker_push.sh
|
||||
on:
|
||||
branch: master
|
||||
- provider: script
|
||||
script: scripts/deploy.sh nitro.human-connection.org
|
||||
on:
|
||||
|
||||
16
README.md
@ -1,10 +1,12 @@
|
||||
# Human Connection - NITRO Web
|
||||
[](https://travis-ci.com/Human-Connection/Nitro-Web)
|
||||
|
||||

|
||||
|
||||
## Build Setup
|
||||
|
||||
|
||||
|
||||
### Install
|
||||
``` bash
|
||||
# install all dependencies
|
||||
@ -36,4 +38,16 @@ All reusable Components (for example avatar) should be done inside the styleguid
|
||||
### To show the styleguide
|
||||
``` bash
|
||||
$ yarn styleguide
|
||||
```
|
||||
```
|
||||
|
||||
## Internationalization (i18n)
|
||||
|
||||
You can help translating the interface by joining us on [lokalise.co](https://lokalise.co/public/556252725c18dd752dd546.13222042/).
|
||||
|
||||
Thanks lokalise.co that we can use your premium account!
|
||||
|
||||
<a href="(https://lokalise.co/public/556252725c18dd752dd546.13222042/)."><img src="lokalise.png" alt="localise.co" height="32px" /></a>
|
||||
|
||||
## Attributions
|
||||
|
||||
<div>Locale Icons made by <a href="http://www.freepik.com/" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div>
|
||||
|
||||
67
components/Dropdown.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<v-popover
|
||||
:open.sync="isPopoverOpen"
|
||||
:open-group="Math.random().toString()"
|
||||
:placement="placement"
|
||||
trigger="manual"
|
||||
:offset="offset"
|
||||
>
|
||||
<slot :toggleMenu="toggleMenu" />
|
||||
<div
|
||||
slot="popover"
|
||||
@mouseover="popoverMouseEnter"
|
||||
@mouseleave="popoveMouseLeave"
|
||||
>
|
||||
<slot
|
||||
name="popover"
|
||||
:toggleMenu="toggleMenu"
|
||||
/>
|
||||
</div>
|
||||
</v-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
let mouseEnterTimer = null
|
||||
let mouseLeaveTimer = null
|
||||
|
||||
export default {
|
||||
props: {
|
||||
placement: { type: String, default: 'bottom-end' },
|
||||
offset: { type: [String, Number], default: '16' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isPopoverOpen: false
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearTimeout(mouseEnterTimer)
|
||||
clearTimeout(mouseLeaveTimer)
|
||||
},
|
||||
methods: {
|
||||
toggleMenu() {
|
||||
this.isPopoverOpen = !this.isPopoverOpen
|
||||
},
|
||||
popoverMouseEnter() {
|
||||
clearTimeout(mouseEnterTimer)
|
||||
clearTimeout(mouseLeaveTimer)
|
||||
if (!this.isPopoverOpen) {
|
||||
mouseEnterTimer = setTimeout(() => {
|
||||
this.isPopoverOpen = true
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
popoveMouseLeave() {
|
||||
clearTimeout(mouseEnterTimer)
|
||||
clearTimeout(mouseLeaveTimer)
|
||||
if (this.isPopoverOpen) {
|
||||
mouseLeaveTimer = setTimeout(() => {
|
||||
this.isPopoverOpen = false
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
114
components/LocaleSwitch.vue
Normal file
@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<dropdown
|
||||
ref="menu"
|
||||
:placement="placement"
|
||||
:offset="offset"
|
||||
>
|
||||
<template
|
||||
slot="default"
|
||||
slot-scope="{toggleMenu}"
|
||||
>
|
||||
<a
|
||||
class="locale-menu"
|
||||
href="#"
|
||||
@click.prevent="toggleMenu()"
|
||||
>
|
||||
<img
|
||||
:alt="current.name"
|
||||
:title="current.name"
|
||||
:src="`/img/locale-flags/${current.code}.svg`"
|
||||
height="26"
|
||||
>
|
||||
</a>
|
||||
</template>
|
||||
<template slot="popover">
|
||||
<ul class="locale-menu-popover">
|
||||
<li
|
||||
v-for="locale in locales"
|
||||
:key="locale.code"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
style="display: flex; align-items: center;"
|
||||
:class="[
|
||||
locale.code,
|
||||
current.code === locale.code && 'active'
|
||||
]"
|
||||
@click.prevent="changeLanguage(locale.code)"
|
||||
>
|
||||
<img
|
||||
:alt="locale.name"
|
||||
:title="locale.name"
|
||||
:src="`/img/locale-flags/${locale.code}.svg`"
|
||||
width="20"
|
||||
> {{ locale.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import find from 'lodash/find'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Dropdown
|
||||
},
|
||||
props: {
|
||||
placement: { type: String, default: 'bottom-start' },
|
||||
offset: { type: [String, Number], default: '16' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
locales: process.env.locales
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
current() {
|
||||
return find(this.locales, { code: this.$i18n.locale() })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeLanguage(locale) {
|
||||
this.$i18n.set(locale)
|
||||
this.$refs.menu.toggleMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.locale-menu {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
ul.locale-menu-popover {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
a {
|
||||
opacity: 0.8;
|
||||
|
||||
display: block;
|
||||
padding: 0.3rem 0;
|
||||
|
||||
img {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
&.active {
|
||||
opacity: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
9
components/mixins/seo.js
Normal file
@ -0,0 +1,9 @@
|
||||
export default {
|
||||
head() {
|
||||
return {
|
||||
htmlAttrs: {
|
||||
lang: this.$i18n.locale()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
cypress.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"projectId": "qa7fe2",
|
||||
"ignoreTestFiles": "*.js"
|
||||
}
|
||||
5
cypress/fixtures/example.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
24
cypress/integration/Internationalization.feature
Normal file
@ -0,0 +1,24 @@
|
||||
Feature: Internationalization
|
||||
As a user who is not very fluent in English
|
||||
I would like to see the user interface translated to my preferred language
|
||||
In order to be able to understand the interface
|
||||
|
||||
Background:
|
||||
Given I am on the "login" page
|
||||
|
||||
Scenario Outline: I select "<language>" in the language menu and see "<buttonLabel>"
|
||||
When I select "<language>" in the language menu
|
||||
Then the whole user interface appears in "<language>"
|
||||
Then I see a button with the label "<buttonLabel>"
|
||||
|
||||
Examples: Login Button
|
||||
| language | buttonLabel |
|
||||
| English | Login |
|
||||
| Deutsch | Einloggen |
|
||||
| Français | Connexion |
|
||||
| Nederlands | Inloggen |
|
||||
|
||||
Scenario: Keep preferred language after refresh
|
||||
Given I previously switched the language to "Deutsch"
|
||||
And I refresh the page
|
||||
Then the whole user interface appears in "Deutsch"
|
||||
25
cypress/integration/Login.feature
Normal file
@ -0,0 +1,25 @@
|
||||
Feature: Authentication
|
||||
As a database administrator
|
||||
I want users to sign in
|
||||
In order to attribute posts and other contributions to their authors
|
||||
|
||||
Background:
|
||||
Given my account has the following details:
|
||||
| name | email | password |
|
||||
| Peter Lustig | admin@example.org | 1234 |
|
||||
|
||||
Scenario: Log in
|
||||
When I visit the "/login" page
|
||||
And I fill in my email and password combination and click submit
|
||||
Then I can click on my profile picture in the top right corner
|
||||
And I can see my name "Peter Lustig" in the dropdown menu
|
||||
|
||||
Scenario: Refresh and stay logged in
|
||||
Given I am logged in
|
||||
When I refresh the page
|
||||
Then I am still logged in
|
||||
|
||||
Scenario: Log out
|
||||
Given I am logged in
|
||||
When I log out through the menu in the top right corner
|
||||
Then I see the login screen again
|
||||
41
cypress/integration/TagsAndCategories.feature
Normal file
@ -0,0 +1,41 @@
|
||||
Feature: Tags and Categories
|
||||
As a database administrator
|
||||
I would like to see a summary of all tags and categories and their usage
|
||||
In order to be able to decide which tags and categories are popular or not
|
||||
|
||||
The currently deployed application, codename "Alpha", distinguishes between
|
||||
categories and tags. Each post can have a number of categories and/or tags.
|
||||
A few categories are required for each post, tags are completely optional.
|
||||
Both help to find relevant posts in the database, e.g. users can filter for
|
||||
categories.
|
||||
|
||||
If administrators summary of all tags and categories and how often they are
|
||||
used, they learn what new category might be convenient for users, e.g. by
|
||||
looking at the popularity of a tag.
|
||||
|
||||
Background:
|
||||
Given we have a selection of tags and categories as well as posts
|
||||
And my user account has the role "administrator"
|
||||
Given I am logged in
|
||||
|
||||
Scenario: See an overview of categories
|
||||
When I navigate to the administration dashboard
|
||||
And I click on "Categories"
|
||||
Then I can see a list of categories ordered by post count:
|
||||
| Icon | Name | Post Count |
|
||||
| | Just For Fun | 5 |
|
||||
| | Happyness & Values | 2 |
|
||||
| | Health & Wellbeing | 1 |
|
||||
|
||||
Scenario: See an overview of tags
|
||||
When I navigate to the administration dashboard
|
||||
And I click on "Tags"
|
||||
Then I can see a list of tags ordered by user and post count:
|
||||
| # | Name | Nutzer | Beiträge |
|
||||
| 1 | Naturschutz | 2 | 2 |
|
||||
| 2 | Freiheit | 2 | 2 |
|
||||
| 3 | Umwelt | 1 | 1 |
|
||||
| 4 | Demokratie | 1 | 1 |
|
||||
|
||||
|
||||
|
||||
161
cypress/integration/common/steps.js
Normal file
@ -0,0 +1,161 @@
|
||||
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
|
||||
import find from 'lodash/find'
|
||||
import { eq } from 'semver';
|
||||
|
||||
/* global cy */
|
||||
|
||||
const baseUrl = 'http://localhost:3000'
|
||||
const username = 'Peter Lustig'
|
||||
|
||||
const locales = require('../../../locales')
|
||||
|
||||
const getLangByName = function(name) {
|
||||
return find(locales, { name })
|
||||
}
|
||||
|
||||
const openPage = function(page) {
|
||||
if (page === 'landing') {
|
||||
page = ''
|
||||
}
|
||||
cy.visit(`${baseUrl}/${page}`)
|
||||
}
|
||||
|
||||
const switchLanguage = function(name) {
|
||||
cy.get('.login-locale-switch a').click()
|
||||
cy.contains('.locale-menu-popover a', name).click()
|
||||
}
|
||||
|
||||
const login = (email, password) => {
|
||||
cy.visit(`${baseUrl}/login`)
|
||||
cy.get('input[name=email]')
|
||||
.trigger('focus')
|
||||
.type(email)
|
||||
cy.get('input[name=password]')
|
||||
.trigger('focus')
|
||||
.type(password)
|
||||
cy.get('button[name=submit]')
|
||||
.as('submitButton')
|
||||
.click()
|
||||
cy.location('pathname').should('eq', '/') // we're in!
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
cy.visit(`${baseUrl}/logout`)
|
||||
cy.location('pathname').should('contain', '/login') // we're out
|
||||
}
|
||||
|
||||
const lastColumnIsSortedInDescendingOrder = () => {
|
||||
cy.get('tbody')
|
||||
.find('tr td:last-child')
|
||||
.then(last_column => {
|
||||
cy.wrap(last_column)
|
||||
const values = last_column
|
||||
.map((i, td) => parseInt(td.textContent))
|
||||
.toArray()
|
||||
const ordered_descending = values.slice(0).sort((a, b) => b - a)
|
||||
return cy.wrap(values).should('deep.eq', ordered_descending)
|
||||
})
|
||||
}
|
||||
|
||||
Given('I am logged in', () => {
|
||||
login('admin@example.org', 1234)
|
||||
})
|
||||
|
||||
Given('we have a selection of tags and categories as well as posts', () => {
|
||||
// TODO: use db factories instead of seed data
|
||||
})
|
||||
|
||||
Given('my account has the following details:', table => {
|
||||
// TODO: use db factories instead of seed data
|
||||
})
|
||||
|
||||
Given('my user account has the role {string}', role => {
|
||||
// TODO: use db factories instead of seed data
|
||||
})
|
||||
|
||||
|
||||
When('I log out', logout)
|
||||
|
||||
When('I visit the {string} page', page => {
|
||||
openPage(page)
|
||||
})
|
||||
Given('I am on the {string} page', page => {
|
||||
openPage(page)
|
||||
})
|
||||
|
||||
When('I fill in my email and password combination and click submit', () => {
|
||||
login('admin@example.org', 1234)
|
||||
})
|
||||
|
||||
When('I refresh the page', () => {
|
||||
cy.reload()
|
||||
})
|
||||
|
||||
When('I log out through the menu in the top right corner', () => {
|
||||
cy.get('.avatar-menu').click()
|
||||
cy.get('.avatar-menu-popover')
|
||||
.find('a')
|
||||
.contains('Logout')
|
||||
.click()
|
||||
})
|
||||
|
||||
Then('I can click on my profile picture in the top right corner', () => {
|
||||
cy.get('.avatar-menu').click()
|
||||
cy.get('.avatar-menu-popover')
|
||||
})
|
||||
|
||||
Then('I can see my name {string} in the dropdown menu', () => {
|
||||
cy.get('.avatar-menu-popover').should('contain', username)
|
||||
})
|
||||
|
||||
Then('I see the login screen again', () => {
|
||||
cy.location('pathname').should('contain', '/login')
|
||||
cy.contains('If you already have a human-connection account, login here.')
|
||||
})
|
||||
|
||||
Then('I am still logged in', () => {
|
||||
cy.get('.avatar-menu').click()
|
||||
cy.get('.avatar-menu-popover').contains(username)
|
||||
})
|
||||
|
||||
When('I select {string} in the language menu', name => {
|
||||
switchLanguage(name)
|
||||
})
|
||||
Given('I previously switched the language to {string}', name => {
|
||||
switchLanguage(name)
|
||||
})
|
||||
Then('the whole user interface appears in {string}', name => {
|
||||
const lang = getLangByName(name)
|
||||
cy.get(`html[lang=${lang.code}]`)
|
||||
cy.getCookie('locale').should('have.property', 'value', lang.code)
|
||||
})
|
||||
Then('I see a button with the label {string}', label => {
|
||||
cy.contains('button', label)
|
||||
})
|
||||
|
||||
When('I navigate to the administration dashboard', () => {
|
||||
cy.get('.avatar-menu').click()
|
||||
cy.get('.avatar-menu-popover')
|
||||
.contains('Admin')
|
||||
.click()
|
||||
})
|
||||
|
||||
When(`I click on {string}`, linkOrButton => {
|
||||
cy.contains(linkOrButton).click()
|
||||
})
|
||||
|
||||
Then('I can see a list of categories ordered by post count:', table => {
|
||||
// TODO: match the table in the feature with the html table
|
||||
cy.get('thead')
|
||||
.find('tr th')
|
||||
.should('have.length', 3)
|
||||
lastColumnIsSortedInDescendingOrder()
|
||||
})
|
||||
|
||||
Then('I can see a list of tags ordered by user and post count:', table => {
|
||||
// TODO: match the table in the feature with the html table
|
||||
cy.get('thead')
|
||||
.find('tr th')
|
||||
.should('have.length', 4)
|
||||
lastColumnIsSortedInDescendingOrder()
|
||||
})
|
||||
20
cypress/plugins/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
const cucumber = require('cypress-cucumber-preprocessor').default
|
||||
module.exports = on => {
|
||||
// (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
on('file:preprocessor', cucumber())
|
||||
}
|
||||
25
cypress/support/commands.js
Normal file
@ -0,0 +1,25 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
20
cypress/support/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
13
docker-compose.override.yml
Normal file
@ -0,0 +1,13 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
webapp:
|
||||
volumes:
|
||||
- .:/nitro-web
|
||||
- node_modules:/nitro-web/node_modules
|
||||
- node_modules_styleguide:/nitro-web/styleguide/node_modules
|
||||
command: yarn run dev
|
||||
|
||||
volumes:
|
||||
node_modules:
|
||||
node_modules_styleguide:
|
||||
@ -2,17 +2,13 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
webapp:
|
||||
build:
|
||||
context: .
|
||||
image: humanconnection/nitro-web:latest
|
||||
build: .
|
||||
ports:
|
||||
- 3000:3000
|
||||
- 8080:8080
|
||||
networks:
|
||||
- hc-network
|
||||
volumes:
|
||||
- .:/HC-WebApp
|
||||
- node_modules:/HC-WebApp/node_modules
|
||||
command: yarn run dev
|
||||
environment:
|
||||
- HOST=0.0.0.0
|
||||
- BACKEND_URL=http://backend:4000
|
||||
|
||||
@ -7,3 +7,11 @@
|
||||
</ds-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import seo from '~/components/mixins/seo'
|
||||
|
||||
export default {
|
||||
mixins: [seo]
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -8,46 +8,63 @@
|
||||
>
|
||||
<ds-logo />
|
||||
</a>
|
||||
<template v-if="isLoggedIn">
|
||||
<div style="float: right">
|
||||
<no-ssr>
|
||||
<v-popover
|
||||
:open.sync="isPopoverOpen"
|
||||
:open-group="Math.random().toString()"
|
||||
placement="bottom-end"
|
||||
trigger="manual"
|
||||
offset="10"
|
||||
style="float: right"
|
||||
>
|
||||
<a
|
||||
:href="$router.resolve({name: 'profile-slug', params: {slug: user.slug}}).href"
|
||||
@click.prevent="toggleMenu()"
|
||||
>
|
||||
<ds-avatar
|
||||
:image="user.avatar"
|
||||
:name="user.name"
|
||||
size="42"
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
slot="popover"
|
||||
style="padding-top: .5rem; padding-bottom: .5rem;"
|
||||
@mouseover="popoverMouseEnter"
|
||||
@mouseleave="popoveMouseLeave"
|
||||
>
|
||||
Hallo {{ user.name }}
|
||||
<ds-menu
|
||||
:routes="routes"
|
||||
style="margin-left: -15px; margin-right: -15px; padding-top: 1rem; padding-bottom: 1rem;"
|
||||
@click.native="toggleMenu"
|
||||
/>
|
||||
<ds-space margin="xx-small" />
|
||||
<nuxt-link :to="{ name: 'logout'}">
|
||||
Logout
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</v-popover>
|
||||
<locale-switch
|
||||
class="topbar-locale-switch"
|
||||
placement="bottom"
|
||||
offset="24"
|
||||
/>
|
||||
</no-ssr>
|
||||
</template>
|
||||
<template v-if="isLoggedIn">
|
||||
<no-ssr>
|
||||
<dropdown class="avatar-menu">
|
||||
<template
|
||||
slot="default"
|
||||
slot-scope="{toggleMenu}"
|
||||
>
|
||||
<a
|
||||
class="avatar-menu-trigger"
|
||||
:href="$router.resolve({name: 'profile-slug', params: {slug: user.slug}}).href"
|
||||
@click.prevent="toggleMenu()"
|
||||
>
|
||||
<ds-avatar
|
||||
:image="user.avatar"
|
||||
:name="user.name"
|
||||
size="42"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
<template
|
||||
slot="popover"
|
||||
slot-scope="{toggleMenu}"
|
||||
>
|
||||
<div class="avatar-menu-popover">
|
||||
{{ $t('login.hello') }} <b>{{ user.name }}</b>
|
||||
<ds-menu
|
||||
:routes="routes"
|
||||
:is-exact="isExact"
|
||||
>
|
||||
<ds-menu-item
|
||||
slot="Navigation"
|
||||
slot-scope="item"
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.native="toggleMenu"
|
||||
>
|
||||
<ds-icon :name="item.route.icon" /> {{ item.route.name }}
|
||||
</ds-menu-item>
|
||||
</ds-menu>
|
||||
<ds-space margin="xx-small" />
|
||||
<nuxt-link :to="{ name: 'logout'}">
|
||||
<ds-icon name="sign-out" /> {{ $t('login.logout') }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
</no-ssr>
|
||||
</template>
|
||||
</div>
|
||||
</ds-container>
|
||||
</div>
|
||||
<ds-container>
|
||||
@ -60,21 +77,21 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { setTimeout } from 'timers'
|
||||
let mouseEnterTimer = null
|
||||
let mouseLeaveTimer = null
|
||||
import LocaleSwitch from '~/components/LocaleSwitch'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import seo from '~/components/mixins/seo'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isPopoverOpen: false
|
||||
}
|
||||
components: {
|
||||
Dropdown,
|
||||
LocaleSwitch
|
||||
},
|
||||
mixins: [seo],
|
||||
computed: {
|
||||
...mapGetters({
|
||||
user: 'auth/user',
|
||||
isLoggedIn: 'auth/isLoggedIn',
|
||||
isAdmin: 'auth/isLoggedIn'
|
||||
isAdmin: 'auth/isAdmin'
|
||||
}),
|
||||
routes() {
|
||||
if (!this.user.slug) {
|
||||
@ -82,49 +99,58 @@ export default {
|
||||
}
|
||||
let routes = [
|
||||
{
|
||||
name: 'Mein Profil',
|
||||
path: `/profile/${this.user.slug}`
|
||||
name: this.$t('profile.name'),
|
||||
path: `/profile/${this.user.slug}`,
|
||||
icon: 'user'
|
||||
},
|
||||
{
|
||||
name: 'Einstellungen',
|
||||
path: `/settings`
|
||||
name: this.$t('settings.name'),
|
||||
path: `/settings`,
|
||||
icon: 'cogs'
|
||||
}
|
||||
]
|
||||
if (this.isAdmin) {
|
||||
routes.push({
|
||||
name: 'Systemverwaltung',
|
||||
path: `/admin`
|
||||
name: this.$t('admin.name'),
|
||||
path: `/admin`,
|
||||
icon: 'shield'
|
||||
})
|
||||
}
|
||||
return routes
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearTimeout(mouseEnterTimer)
|
||||
clearTimeout(mouseLeaveTimer)
|
||||
},
|
||||
methods: {
|
||||
toggleMenu() {
|
||||
this.isPopoverOpen = !this.isPopoverOpen
|
||||
},
|
||||
popoverMouseEnter() {
|
||||
clearTimeout(mouseEnterTimer)
|
||||
clearTimeout(mouseLeaveTimer)
|
||||
if (!this.isPopoverOpen) {
|
||||
mouseEnterTimer = setTimeout(() => {
|
||||
this.isPopoverOpen = true
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
popoveMouseLeave() {
|
||||
clearTimeout(mouseEnterTimer)
|
||||
clearTimeout(mouseLeaveTimer)
|
||||
if (this.isPopoverOpen) {
|
||||
mouseLeaveTimer = setTimeout(() => {
|
||||
this.isPopoverOpen = false
|
||||
}, 300)
|
||||
}
|
||||
isExact(url) {
|
||||
return this.$route.path.indexOf(url) === 0
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.topbar-locale-switch {
|
||||
display: inline-block;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.avatar-menu {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.avatar-menu-trigger {
|
||||
user-select: none;
|
||||
}
|
||||
.avatar-menu-popover {
|
||||
display: inline-block;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
|
||||
nav {
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
97
locales/de.json
Normal file
@ -0,0 +1,97 @@
|
||||
{
|
||||
"login": {
|
||||
"copy": "Wenn Du ein Konto bei Human Connection hast, melde Dich bitte hier an.",
|
||||
"login": "Einloggen",
|
||||
"logout": "Ausloggen",
|
||||
"email": "Deine E-Mail",
|
||||
"password": "Dein Passwort",
|
||||
"moreInfo": "Was ist Human Connection?",
|
||||
"hello": "Hallo"
|
||||
},
|
||||
"profile": {
|
||||
"name": "Mein Profil",
|
||||
"memberSince": "Mitglied seit",
|
||||
"follow": "Folgen",
|
||||
"followers": "Folgen",
|
||||
"following": "Folgt"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Einstellungen",
|
||||
"data": {
|
||||
"name": "Deine Daten"
|
||||
},
|
||||
"security": {
|
||||
"name": "Sicherheit"
|
||||
},
|
||||
"invites": {
|
||||
"name": "Einladungen"
|
||||
},
|
||||
"download": {
|
||||
"name": "Daten herunterladen"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Konto löschen"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Meine Organisationen"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Sprachen"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "Systemverwaltung",
|
||||
"dashboard": {
|
||||
"name": "Startzentrale",
|
||||
"users": "Benutzer",
|
||||
"posts": "Beiträge",
|
||||
"comments": "Kommentare",
|
||||
"notifications": "Benachrichtigungen",
|
||||
"organizations": "Organisationen",
|
||||
"projects": "Projekte",
|
||||
"invites": "Einladungen",
|
||||
"follows": "Folgen",
|
||||
"shouts": "Shouts"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organisationen"
|
||||
},
|
||||
"users": {
|
||||
"name": "Benutzer"
|
||||
},
|
||||
"pages": {
|
||||
"name": "Seiten"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Benachrichtigungen"
|
||||
},
|
||||
"categories": {
|
||||
"name": "Kategorien",
|
||||
"categoryName": "Name",
|
||||
"postCount": "Beiträge"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Schlagworte",
|
||||
"tagCountUnique": "Benutzer",
|
||||
"tagCount": "Beiträge"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Einstellungen"
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"name": "Beitrag",
|
||||
"moreInfo": {
|
||||
"name": "Mehr Info"
|
||||
},
|
||||
"takeAction": {
|
||||
"name": "Aktiv werden"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"quote": "Viele kleine Leute, an vielen kleinen Orten, die viele kleine Dinge tun, werden das Antlitz dieser Welt verändern.",
|
||||
"author": "Afrikanisches Sprichwort"
|
||||
}
|
||||
}
|
||||
}
|
||||
97
locales/en.json
Normal file
@ -0,0 +1,97 @@
|
||||
{
|
||||
"login": {
|
||||
"copy": "If you already have a human-connection account, login here.",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"email": "Your Email",
|
||||
"password": "Your Password",
|
||||
"moreInfo": "What is Human Connection?",
|
||||
"hello": "Hello"
|
||||
},
|
||||
"profile": {
|
||||
"name": "My Profile",
|
||||
"memberSince": "Member since",
|
||||
"follow": "Follow",
|
||||
"followers": "Followers",
|
||||
"following": "Following"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Settings",
|
||||
"data": {
|
||||
"name": "Your data"
|
||||
},
|
||||
"security": {
|
||||
"name": "Security"
|
||||
},
|
||||
"invites": {
|
||||
"name": "Invites"
|
||||
},
|
||||
"download": {
|
||||
"name": "Download Data"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Delete Account"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "My Organizations"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "Admin",
|
||||
"dashboard": {
|
||||
"name": "Dashboard",
|
||||
"users": "Users",
|
||||
"posts": "Posts",
|
||||
"comments": "Comments",
|
||||
"notifications": "Notifications",
|
||||
"organizations": "Organizations",
|
||||
"projects": "Projects",
|
||||
"invites": "Invites",
|
||||
"follows": "Follows",
|
||||
"shouts": "Shouts"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organizations"
|
||||
},
|
||||
"users": {
|
||||
"name": "Users"
|
||||
},
|
||||
"pages": {
|
||||
"name": "Pages"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Notifications"
|
||||
},
|
||||
"categories": {
|
||||
"name": "Categories",
|
||||
"categoryName": "Name",
|
||||
"postCount": "Posts"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Tags",
|
||||
"tagCountUnique": "Users",
|
||||
"tagCount": "Posts"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Settings"
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"name": "Post",
|
||||
"moreInfo": {
|
||||
"name": "More info"
|
||||
},
|
||||
"takeAction": {
|
||||
"name": "Take action"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"quote": "Many small people in many small places do many small things, that can alter the face of the world.",
|
||||
"author": "African proverb"
|
||||
}
|
||||
}
|
||||
}
|
||||
79
locales/es.json
Normal file
@ -0,0 +1,79 @@
|
||||
{
|
||||
"login": {
|
||||
"copy": "Si ya tiene una cuenta de Human Connection, inicie sesión aquí.",
|
||||
"logout": "Cierre de sesión",
|
||||
"email": "Tu correo electrónico",
|
||||
"password": "Tu contraseña",
|
||||
"moreInfo": "¿Qué es Human Connection?",
|
||||
"hello": "Hola"
|
||||
},
|
||||
"profile": {
|
||||
"name": "Mi perfil",
|
||||
"memberSince": "Miembro desde",
|
||||
"followers": "Seguidores"
|
||||
},
|
||||
"settings": {
|
||||
"data": {
|
||||
"name": "Sus datos"
|
||||
},
|
||||
"security": {
|
||||
"name": "Seguridad"
|
||||
},
|
||||
"invites": {
|
||||
"name": "Invita"
|
||||
},
|
||||
"download": {
|
||||
"name": "Descargar datos"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Mis organizaciones"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Idiomas"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "Admin",
|
||||
"dashboard": {
|
||||
"users": "Usuarios",
|
||||
"comments": "Comentarios",
|
||||
"organizations": "Organizaciones",
|
||||
"projects": "Proyectos",
|
||||
"invites": "Invita",
|
||||
"follows": "Sigue"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organizaciones"
|
||||
},
|
||||
"users": {
|
||||
"name": "Usuarios"
|
||||
},
|
||||
"pages": {
|
||||
"name": "Páginas"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Notificaciones"
|
||||
},
|
||||
"categories": {
|
||||
"name": "Categorías",
|
||||
"categoryName": "Nombre"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Etiquetas",
|
||||
"tagCountUnique": "Usuarios"
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"moreInfo": {
|
||||
"name": "Más info"
|
||||
},
|
||||
"takeAction": {
|
||||
"name": "Tomar acción"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"author": "Proverbio africano"
|
||||
}
|
||||
}
|
||||
}
|
||||
97
locales/fr.json
Normal file
@ -0,0 +1,97 @@
|
||||
{
|
||||
"login": {
|
||||
"copy": "Si vous avez déjà un compte human-connection, connectez-vous ici.",
|
||||
"login": "Connexion",
|
||||
"logout": "Déconnexion",
|
||||
"email": "Votre Message électronique",
|
||||
"password": "Votre mot de passe",
|
||||
"moreInfo": "Qu'est-ce que Human Connection?",
|
||||
"hello": "Bonjour"
|
||||
},
|
||||
"profile": {
|
||||
"name": "Mon profil",
|
||||
"memberSince": "Membre depuis",
|
||||
"follow": "Suivre",
|
||||
"followers": "Suiveurs",
|
||||
"following": "Suivant"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Paramètres",
|
||||
"data": {
|
||||
"name": "Vos données"
|
||||
},
|
||||
"security": {
|
||||
"name": "Sécurité"
|
||||
},
|
||||
"invites": {
|
||||
"name": "Invite"
|
||||
},
|
||||
"download": {
|
||||
"name": "Télécharger les données"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Supprimer un compte"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Mes organisations"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Langues"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "Admin",
|
||||
"dashboard": {
|
||||
"name": "Tableau de bord",
|
||||
"users": "Utilisateurs",
|
||||
"posts": "Postes",
|
||||
"comments": "Commentaires",
|
||||
"notifications": "Notifications",
|
||||
"organizations": "Organisations",
|
||||
"projects": "Projets",
|
||||
"invites": "Invite",
|
||||
"follows": "Suit",
|
||||
"shouts": "Cris"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organisations"
|
||||
},
|
||||
"users": {
|
||||
"name": "Utilisateurs"
|
||||
},
|
||||
"pages": {
|
||||
"name": "Pages"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Notifications"
|
||||
},
|
||||
"categories": {
|
||||
"name": "Catégories",
|
||||
"categoryName": "Nom",
|
||||
"postCount": "Postes"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Étiquettes",
|
||||
"tagCountUnique": "Utilisateurs",
|
||||
"tagCount": "Postes"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Paramètres"
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"name": "Post",
|
||||
"moreInfo": {
|
||||
"name": "Plus d'infos"
|
||||
},
|
||||
"takeAction": {
|
||||
"name": "Passez à l'action"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"quote": "Beaucoup de petites personnes dans beaucoup de petits endroits font beaucoup de petites choses, qui peuvent changer la face du monde.",
|
||||
"author": "Proverbe africain"
|
||||
}
|
||||
}
|
||||
}
|
||||
50
locales/index.js
Normal file
@ -0,0 +1,50 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'English',
|
||||
code: 'en',
|
||||
iso: 'en-US',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
name: 'Deutsch',
|
||||
code: 'de',
|
||||
iso: 'de-DE',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
name: 'Nederlands',
|
||||
code: 'nl',
|
||||
iso: 'nl-NL',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
name: 'Français',
|
||||
code: 'fr',
|
||||
iso: 'fr-FR',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
name: 'Italiano',
|
||||
code: 'it',
|
||||
iso: 'it-IT',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
name: 'Español',
|
||||
code: 'es',
|
||||
iso: 'es-ES',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
name: 'Portuguese',
|
||||
code: 'pt',
|
||||
iso: 'pt-PT',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
name: 'Polski',
|
||||
code: 'pl',
|
||||
iso: 'pl-PL',
|
||||
enabled: true
|
||||
}
|
||||
]
|
||||
89
locales/it.json
Normal file
@ -0,0 +1,89 @@
|
||||
{
|
||||
"login": {
|
||||
"copy": "Se hai già un account di Human Connection, accedi qui.",
|
||||
"logout": "Logout",
|
||||
"email": "La tua email",
|
||||
"password": "La tua password",
|
||||
"moreInfo": "Che cosa è Human Connection?",
|
||||
"hello": "Ciao"
|
||||
},
|
||||
"profile": {
|
||||
"name": "Il mio profilo",
|
||||
"follow": "Seguire",
|
||||
"followers": "Seguaci"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Impostazioni",
|
||||
"data": {
|
||||
"name": "I tuoi dati"
|
||||
},
|
||||
"security": {
|
||||
"name": "Sicurezza"
|
||||
},
|
||||
"invites": {
|
||||
"name": "Inviti"
|
||||
},
|
||||
"download": {
|
||||
"name": "Scaricare i dati"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Elimina Account"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Mie organizzazioni"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Lingue"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "Admin",
|
||||
"dashboard": {
|
||||
"name": "Cruscotto",
|
||||
"users": "Utenti",
|
||||
"comments": "Commenti",
|
||||
"notifications": "Notifiche",
|
||||
"organizations": "Organizzazioni",
|
||||
"projects": "Progetti",
|
||||
"invites": "Inviti",
|
||||
"follows": "Segue"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organizzazioni"
|
||||
},
|
||||
"users": {
|
||||
"name": "Utenti"
|
||||
},
|
||||
"pages": {
|
||||
"name": "Pagine"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Notifiche"
|
||||
},
|
||||
"categories": {
|
||||
"name": "Categorie",
|
||||
"categoryName": "Nome"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Tag",
|
||||
"tagCountUnique": "Utenti",
|
||||
"tagCount": "Messaggi"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Impostazioni"
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"moreInfo": {
|
||||
"name": "Ulteriori informazioni"
|
||||
},
|
||||
"takeAction": {
|
||||
"name": "Agire"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"author": "Proverbio africano"
|
||||
}
|
||||
}
|
||||
}
|
||||
90
locales/nl.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"login": {
|
||||
"copy": "Als u al een mini-aansluiting account heeft, log dan hier in.",
|
||||
"login": "Inloggen",
|
||||
"logout": "Uitloggen",
|
||||
"email": "Uw E-mail",
|
||||
"password": "Uw Wachtwoord",
|
||||
"moreInfo": "Wat is Human Connection?",
|
||||
"hello": "Hallo"
|
||||
},
|
||||
"profile": {
|
||||
"name": "Mijn profiel",
|
||||
"memberSince": "Lid sinds",
|
||||
"follow": "Volgen",
|
||||
"followers": "Volgelingen",
|
||||
"following": "Volgt"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Instellingen",
|
||||
"data": {
|
||||
"name": "Uw gegevens"
|
||||
},
|
||||
"security": {
|
||||
"name": "Veiligheid"
|
||||
},
|
||||
"download": {
|
||||
"name": "Gegevens downloaden"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Account verwijderen"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Mijn Organisaties"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Talen"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "Admin",
|
||||
"dashboard": {
|
||||
"name": "Dashboard",
|
||||
"users": "Gebruikers",
|
||||
"posts": "Berichten",
|
||||
"comments": "Opmerkingen",
|
||||
"notifications": "Meldingen",
|
||||
"organizations": "Organisaties",
|
||||
"projects": "Projecten",
|
||||
"follows": "Volgt",
|
||||
"shouts": "Shouts"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organisaties"
|
||||
},
|
||||
"users": {
|
||||
"name": "Gebruikers"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Meldingen"
|
||||
},
|
||||
"categories": {
|
||||
"name": "Categorieën",
|
||||
"categoryName": "Naam",
|
||||
"postCount": "Berichten"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Tags",
|
||||
"tagCountUnique": "Gebruikers",
|
||||
"tagCount": "Berichten"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Instellingen"
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"name": "Post",
|
||||
"moreInfo": {
|
||||
"name": "Meer info"
|
||||
},
|
||||
"takeAction": {
|
||||
"name": "Onderneem actie"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"quote": "Veel kleine mensen op veel kleine plaatsen doen veel kleine dingen, die het gezicht van de wereld kunnen veranderen.",
|
||||
"author": "Afrikaans spreekwoord"
|
||||
}
|
||||
}
|
||||
}
|
||||
97
locales/pl.json
Normal file
@ -0,0 +1,97 @@
|
||||
{
|
||||
"login": {
|
||||
"copy": "Jeśli masz już konto Human Connection, zaloguj się tutaj.",
|
||||
"login": "Login",
|
||||
"logout": "Wyloguj się",
|
||||
"email": "Twój adres e-mail",
|
||||
"password": "Twoje hasło",
|
||||
"moreInfo": "Co to jest Human Connection?",
|
||||
"hello": "Cześć"
|
||||
},
|
||||
"profile": {
|
||||
"name": "Mój profil",
|
||||
"memberSince": "Członek od",
|
||||
"follow": "Obserwuj",
|
||||
"followers": "Obserwujący",
|
||||
"following": "Obserwowani"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Ustawienia",
|
||||
"data": {
|
||||
"name": "Twoje dane"
|
||||
},
|
||||
"security": {
|
||||
"name": "Bezpieczeństwo"
|
||||
},
|
||||
"invites": {
|
||||
"name": "Zaproszenia"
|
||||
},
|
||||
"download": {
|
||||
"name": "Pobierz dane"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Usuń konto"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Moje organizacje"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Języki"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "Admin",
|
||||
"dashboard": {
|
||||
"name": "Tablica rozdzielcza",
|
||||
"users": "Użytkownicy",
|
||||
"posts": "Posty",
|
||||
"comments": "Komentarze",
|
||||
"notifications": "Powiadomienia",
|
||||
"organizations": "Organizacje",
|
||||
"projects": "Projekty",
|
||||
"invites": "Zaproszenia",
|
||||
"follows": "Obserwowań",
|
||||
"shouts": "Wykrzyki"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organizacje"
|
||||
},
|
||||
"users": {
|
||||
"name": "Użytkownicy"
|
||||
},
|
||||
"pages": {
|
||||
"name": "Strony"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Powiadomienia"
|
||||
},
|
||||
"categories": {
|
||||
"name": "Kategorie",
|
||||
"categoryName": "Nazwa",
|
||||
"postCount": "Posty"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Tagi",
|
||||
"tagCountUnique": "Użytkownicy",
|
||||
"tagCount": "Posty"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Ustawienia"
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"name": "Post",
|
||||
"moreInfo": {
|
||||
"name": "Więcej informacji"
|
||||
},
|
||||
"takeAction": {
|
||||
"name": "Podejmij działanie"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"quote": "Wielu małych ludzi w wielu małych miejscach robi wiele małych rzeczy, które mogą zmienić oblicze świata.",
|
||||
"author": "Afrykańskie przysłowie"
|
||||
}
|
||||
}
|
||||
}
|
||||
96
locales/pt.json
Normal file
@ -0,0 +1,96 @@
|
||||
{
|
||||
"login": {
|
||||
"copy": "Se você já tem uma conta no Human Connection, faça o login aqui.",
|
||||
"login": "Login",
|
||||
"logout": "Sair",
|
||||
"email": "Seu email",
|
||||
"password": "Sua senha",
|
||||
"moreInfo": "O que é o Human Connection?",
|
||||
"hello": "Olá"
|
||||
},
|
||||
"profile": {
|
||||
"name": "Meu perfil",
|
||||
"memberSince": "Membro desde",
|
||||
"follow": "Seguir",
|
||||
"followers": "Seguidores",
|
||||
"following": "Seguindo"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Configurações",
|
||||
"data": {
|
||||
"name": "Seus Dados"
|
||||
},
|
||||
"security": {
|
||||
"name": "Segurança"
|
||||
},
|
||||
"invites": {
|
||||
"name": "Convites"
|
||||
},
|
||||
"download": {
|
||||
"name": "Baixar dados"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Deletar conta"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Minhas Organizações"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Linguagens"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "Administrator",
|
||||
"dashboard": {
|
||||
"name": "Painel de controle",
|
||||
"users": "Usuários",
|
||||
"posts": "Postagens",
|
||||
"comments": "Comentários",
|
||||
"notifications": "Notificações",
|
||||
"organizations": "Organizações",
|
||||
"projects": "Projetos",
|
||||
"invites": "Convites",
|
||||
"follows": "Seguidores"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organizações"
|
||||
},
|
||||
"users": {
|
||||
"name": "Usuários"
|
||||
},
|
||||
"pages": {
|
||||
"name": "Páginas"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Notificações"
|
||||
},
|
||||
"categories": {
|
||||
"name": "Categorias",
|
||||
"categoryName": "Nome",
|
||||
"postCount": "Postagens"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Etiquetas",
|
||||
"tagCountUnique": "Usuários",
|
||||
"tagCount": "Postagens"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Configurações"
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"name": "Postar",
|
||||
"moreInfo": {
|
||||
"name": "Mais informações"
|
||||
},
|
||||
"takeAction": {
|
||||
"name": "Tomar uma ação"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"quote": "Pequenos grupos de pessoas, em pequenos locais podem fazer várias coisas pequenas, mas que podem alterar o mundo ao nosso redor.",
|
||||
"author": "Provérbio Africano"
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
lokalise.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
@ -1,4 +1,4 @@
|
||||
import { isEmpty } from 'lodash'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
|
||||
export default async ({ store, env, route, redirect }) => {
|
||||
let publicPages = env.publicPages
|
||||
|
||||
@ -26,7 +26,9 @@ module.exports = {
|
||||
'pages-slug'
|
||||
],
|
||||
// pages to keep alive
|
||||
keepAlivePages: ['index']
|
||||
keepAlivePages: ['index'],
|
||||
// active locales
|
||||
locales: require('./locales')
|
||||
},
|
||||
/*
|
||||
** Headers of the page
|
||||
@ -59,6 +61,7 @@ module.exports = {
|
||||
** Plugins to load before mounting the App
|
||||
*/
|
||||
plugins: [
|
||||
{ src: '~/plugins/i18n.js', ssr: true },
|
||||
{ src: '~/plugins/keep-alive.js', ssr: false },
|
||||
{ src: '~/plugins/design-system.js', ssr: true },
|
||||
{ src: '~/plugins/vue-directives.js', ssr: false },
|
||||
@ -71,30 +74,6 @@ module.exports = {
|
||||
middleware: ['authenticated'],
|
||||
linkActiveClass: 'router-active-link'
|
||||
},
|
||||
/* router: {
|
||||
routes: [
|
||||
{
|
||||
name: 'index',
|
||||
path: '/',
|
||||
component: 'pages/index.vue'
|
||||
},
|
||||
{
|
||||
name: 'post-slug',
|
||||
path: '/post/:slug',
|
||||
component: 'pages/post/_slug.vue',
|
||||
children: [
|
||||
{
|
||||
path: 'more-info',
|
||||
component: 'pages/post/_slug.vue'
|
||||
},
|
||||
{
|
||||
path: 'take-action',
|
||||
component: 'pages/post/_slug.vue'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}, */
|
||||
|
||||
/*
|
||||
** Nuxt.js modules
|
||||
@ -102,6 +81,7 @@ module.exports = {
|
||||
modules: [
|
||||
['@nuxtjs/dotenv', { only: envWhitelist }],
|
||||
['nuxt-env', { keys: envWhitelist }],
|
||||
'cookie-universal-nuxt',
|
||||
'@nuxtjs/apollo',
|
||||
'@nuxtjs/axios',
|
||||
[
|
||||
@ -172,6 +152,7 @@ module.exports = {
|
||||
*/
|
||||
build: {
|
||||
/*
|
||||
* TODO: import the polyfill instead of using the deprecated vendor key
|
||||
* Polyfill missing ES6 & 7 Methods to work on older Browser
|
||||
*/
|
||||
vendor: ['@babel/polyfill'],
|
||||
|
||||
26
package.json
@ -15,6 +15,9 @@
|
||||
"test": "jest",
|
||||
"precommit": "yarn lint"
|
||||
},
|
||||
"cypress-cucumber-preprocessor": {
|
||||
"nonGlobalStepDefinitions": true
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
@ -29,15 +32,19 @@
|
||||
"^@/(.*)$": "<rootDir>/src/$1",
|
||||
"^@@/(.*)$": "<rootDir>/styleguide/src/system/$1",
|
||||
"^~/(.*)$": "<rootDir>/$1"
|
||||
}
|
||||
},
|
||||
"modulePathIgnorePatterns": [
|
||||
"<rootDir>/styleguide"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxtjs/apollo": "^4.0.0-rc3",
|
||||
"@nuxtjs/axios": "^5.3.6",
|
||||
"@nuxtjs/dotenv": "^1.3.0",
|
||||
"accounting": "^0.4.1",
|
||||
"cookie-universal-nuxt": "^2.0.11",
|
||||
"cross-env": "^5.2.0",
|
||||
"date-fns": "^2.0.0-alpha.24",
|
||||
"date-fns": "^2.0.0-alpha.26",
|
||||
"express": "^4.16.3",
|
||||
"graphql": "^14.0.2",
|
||||
"graphql-tag": "^2.10.0",
|
||||
@ -46,26 +53,29 @@
|
||||
"nuxt-env": "^0.0.4",
|
||||
"v-tooltip": "^2.0.0-rc.33",
|
||||
"vue-count-to": "^1.0.13",
|
||||
"vue-izitoast": "1.1.2"
|
||||
"vue-izitoast": "1.1.2",
|
||||
"vuex-i18n": "^1.10.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/eslint-config-prettier": "^4.0.1",
|
||||
"@vue/test-utils": "^1.0.0-beta.25",
|
||||
"@vue/server-test-utils": "^1.0.0-beta.27",
|
||||
"@vue/test-utils": "^1.0.0-beta.27",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"eslint": "^5.0.1",
|
||||
"cypress-cucumber-preprocessor": "^1.9.1",
|
||||
"eslint": "^5.11.0",
|
||||
"eslint-config-prettier": "^3.1.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-prettier": "3.0.0",
|
||||
"eslint-plugin-vue": "^5.0.0",
|
||||
"jest": "^23.6.0",
|
||||
"node-sass": "^4.9.3",
|
||||
"nodemon": "^1.11.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"nodemon": "^1.18.9",
|
||||
"nuxt-sass-resources-loader": "^2.0.5",
|
||||
"prettier": "1.14.3",
|
||||
"sass-loader": "^7.1.0",
|
||||
"vue-jest": "^3.0.0",
|
||||
"vue-jest": "^3.0.2",
|
||||
"vue-svg-loader": "^0.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-heading tag="h1">
|
||||
Administartion
|
||||
{{ $t('admin.name') }}
|
||||
</ds-heading>
|
||||
<ds-flex gutter="small">
|
||||
<ds-flex-item :width="{ base: '200px' }">
|
||||
@ -21,39 +21,39 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
routes: [
|
||||
computed: {
|
||||
routes() {
|
||||
return [
|
||||
{
|
||||
name: 'Dashboard',
|
||||
name: this.$t('admin.dashboard.name'),
|
||||
path: `/admin`
|
||||
},
|
||||
{
|
||||
name: 'Users',
|
||||
name: this.$t('admin.users.name'),
|
||||
path: `/admin/users`
|
||||
},
|
||||
{
|
||||
name: 'Organizations',
|
||||
name: this.$t('admin.organizations.name'),
|
||||
path: `/admin/organizations`
|
||||
},
|
||||
{
|
||||
name: 'Pages',
|
||||
name: this.$t('admin.pages.name'),
|
||||
path: `/admin/pages`
|
||||
},
|
||||
{
|
||||
name: 'Notifications',
|
||||
name: this.$t('admin.notifications.name'),
|
||||
path: `/admin/notifications`
|
||||
},
|
||||
{
|
||||
name: 'Categories',
|
||||
name: this.$t('admin.categories.name'),
|
||||
path: `/admin/categories`
|
||||
},
|
||||
{
|
||||
name: 'Tags',
|
||||
name: this.$t('admin.tags.name'),
|
||||
path: `/admin/tags`
|
||||
},
|
||||
{
|
||||
name: 'Settings',
|
||||
name: this.$t('admin.settings.name'),
|
||||
path: `/admin/settings`
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<ds-card space="small">
|
||||
<ds-heading tag="h3">
|
||||
Themen / Kategorien
|
||||
{{ $t('admin.categories.name') }}
|
||||
</ds-heading>
|
||||
<ds-table
|
||||
:data="Category"
|
||||
:fields="['icon', 'name', 'postCount']"
|
||||
:fields="fields"
|
||||
condensed
|
||||
>
|
||||
<template
|
||||
@ -27,6 +27,18 @@ export default {
|
||||
Category: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fields() {
|
||||
return {
|
||||
icon: ' ',
|
||||
name: this.$t('admin.categories.categoryName'),
|
||||
postCount: {
|
||||
label: this.$t('admin.categories.postCount'),
|
||||
align: 'right'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
Category: {
|
||||
query: gql(`
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<ds-space margin="small">
|
||||
<ds-number
|
||||
:count="0"
|
||||
label="Users"
|
||||
:label="$t('admin.dashboard.users')"
|
||||
size="x-large"
|
||||
uppercase
|
||||
>
|
||||
@ -24,7 +24,7 @@
|
||||
<ds-space margin="small">
|
||||
<ds-number
|
||||
:count="0"
|
||||
label="Posts"
|
||||
:label="$t('admin.dashboard.posts')"
|
||||
size="x-large"
|
||||
uppercase
|
||||
>
|
||||
@ -41,7 +41,7 @@
|
||||
<ds-space margin="small">
|
||||
<ds-number
|
||||
:count="0"
|
||||
label="Comments"
|
||||
:label="$t('admin.dashboard.comments')"
|
||||
size="x-large"
|
||||
uppercase
|
||||
>
|
||||
@ -58,7 +58,7 @@
|
||||
<ds-space margin="small">
|
||||
<ds-number
|
||||
:count="0"
|
||||
label="Notifications"
|
||||
:label="$t('admin.dashboard.notifications')"
|
||||
size="x-large"
|
||||
uppercase
|
||||
>
|
||||
@ -75,7 +75,7 @@
|
||||
<ds-space margin="small">
|
||||
<ds-number
|
||||
:count="0"
|
||||
label="Organization"
|
||||
:label="$t('admin.dashboard.organizations')"
|
||||
size="x-large"
|
||||
uppercase
|
||||
>
|
||||
@ -92,7 +92,7 @@
|
||||
<ds-space margin="small">
|
||||
<ds-number
|
||||
:count="0"
|
||||
label="Projects"
|
||||
:label="$t('admin.dashboard.projects')"
|
||||
size="x-large"
|
||||
uppercase
|
||||
>
|
||||
@ -109,7 +109,7 @@
|
||||
<ds-space margin="small">
|
||||
<ds-number
|
||||
:count="0"
|
||||
label="Open Invites"
|
||||
:label="$t('admin.dashboard.invites')"
|
||||
size="x-large"
|
||||
uppercase
|
||||
>
|
||||
@ -126,7 +126,7 @@
|
||||
<ds-space margin="small">
|
||||
<ds-number
|
||||
:count="0"
|
||||
label="Follows"
|
||||
:label="$t('admin.dashboard.follows')"
|
||||
size="x-large"
|
||||
uppercase
|
||||
>
|
||||
@ -143,7 +143,7 @@
|
||||
<ds-space margin="small">
|
||||
<ds-number
|
||||
:count="0"
|
||||
label="Shouts"
|
||||
:label="$t('admin.dashboard.shouts')"
|
||||
size="x-large"
|
||||
uppercase
|
||||
>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
Notifications...
|
||||
</ds-space>
|
||||
<ds-card space="small">
|
||||
<ds-heading tag="h3">
|
||||
{{ $t('admin.notifications.name') }}
|
||||
</ds-heading>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
Organizations...
|
||||
{{ $t('admin.organizations.name') }}
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
Pages...
|
||||
{{ $t('admin.pages.name') }}
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
Settings...
|
||||
{{ $t('admin.settings.name') }}
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card space="small">
|
||||
<ds-heading tag="h3">
|
||||
Tags
|
||||
{{ $t('admin.tags.name') }}
|
||||
</ds-heading>
|
||||
<ds-table
|
||||
:data="Tag"
|
||||
@ -24,12 +24,22 @@ import gql from 'graphql-tag'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
Tag: [],
|
||||
fields: {
|
||||
id: { label: '#' },
|
||||
name: { label: 'Name' },
|
||||
taggedCountUnique: { label: 'Nutzer' },
|
||||
taggedCount: { label: 'Beiträge' }
|
||||
Tag: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fields() {
|
||||
return {
|
||||
id: '#',
|
||||
name: 'Name',
|
||||
taggedCountUnique: {
|
||||
label: this.$t('admin.tags.tagCountUnique'),
|
||||
align: 'right'
|
||||
},
|
||||
taggedCount: {
|
||||
label: this.$t('admin.tags.tagCount'),
|
||||
align: 'right'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
Users...
|
||||
{{ $t('admin.users.name') }}
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
184
pages/login.vue
@ -1,93 +1,112 @@
|
||||
<template>
|
||||
<ds-container width="small">
|
||||
<ds-space margin="small">
|
||||
<blockquote>
|
||||
<p>
|
||||
Viele kleine Leute, an vielen kleinen Orten, die viele kleine Dinge tun, werden das Antlitz dieser Welt verändern.
|
||||
</p>
|
||||
<b>- Afrikanisches Sprichwort</b>
|
||||
</blockquote>
|
||||
</ds-space>
|
||||
<ds-card>
|
||||
<ds-flex gutter="small">
|
||||
<ds-flex-item
|
||||
:width="{ base: '100%', sm: '50%' }"
|
||||
center
|
||||
>
|
||||
<ds-space
|
||||
margin-top="small"
|
||||
margin-bottom="xxx-small"
|
||||
<transition
|
||||
name="fade"
|
||||
appear
|
||||
>
|
||||
<ds-container
|
||||
v-if="ready"
|
||||
width="small"
|
||||
>
|
||||
<ds-space margin="small">
|
||||
<blockquote>
|
||||
<p>{{ $t('quotes.african.quote') }}</p>
|
||||
<b>- {{ $t('quotes.african.author') }}</b>
|
||||
</blockquote>
|
||||
</ds-space>
|
||||
<ds-card class="login-card">
|
||||
<ds-flex gutter="small">
|
||||
<ds-flex-item
|
||||
:width="{ base: '100%', sm: '50%' }"
|
||||
center
|
||||
>
|
||||
<img
|
||||
class="login-image"
|
||||
src="/img/sign-up/humanconnection.svg"
|
||||
alt="Human Connection"
|
||||
<no-ssr>
|
||||
<locale-switch
|
||||
class="login-locale-switch"
|
||||
offset="5"
|
||||
/>
|
||||
</no-ssr>
|
||||
<ds-space
|
||||
margin-top="small"
|
||||
margin-bottom="xxx-small"
|
||||
center
|
||||
>
|
||||
</ds-space>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
:width="{ base: '100%', sm: '50%' }"
|
||||
center
|
||||
>
|
||||
<ds-space margin="small">
|
||||
<ds-text size="small">
|
||||
Wenn Du ein Konto bei Human Connection hast, melde Dich bitte hier an.
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<form
|
||||
:disabled="pending"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<ds-input
|
||||
v-model="form.email"
|
||||
:disabled="pending"
|
||||
placeholder="Deine E-Mail"
|
||||
type="email"
|
||||
name="email"
|
||||
icon="envelope"
|
||||
/>
|
||||
<ds-input
|
||||
v-model="form.password"
|
||||
:disabled="pending"
|
||||
placeholder="Dein Password"
|
||||
icon="lock"
|
||||
icon-right="question-circle"
|
||||
name="password"
|
||||
type="password"
|
||||
/>
|
||||
<ds-button
|
||||
:loading="pending"
|
||||
primary
|
||||
full-width
|
||||
name="submit"
|
||||
type="submit"
|
||||
>
|
||||
Anmelden
|
||||
</ds-button>
|
||||
<ds-space margin="x-small">
|
||||
<a
|
||||
href="https://human-connection.org"
|
||||
title="zur Präsentationsseite"
|
||||
target="_blank"
|
||||
<img
|
||||
class="login-image"
|
||||
src="/img/sign-up/humanconnection.svg"
|
||||
alt="Human Connection"
|
||||
>
|
||||
Was ist Human Connection?
|
||||
</a>
|
||||
</ds-space>
|
||||
</form>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-card>
|
||||
</ds-container>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
:width="{ base: '100%', sm: '50%' }"
|
||||
center
|
||||
>
|
||||
<ds-space margin="small">
|
||||
<ds-text size="small">
|
||||
{{ $t('login.copy') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<form
|
||||
:disabled="pending"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<ds-input
|
||||
v-model="form.email"
|
||||
:disabled="pending"
|
||||
:placeholder="$t('login.email')"
|
||||
type="email"
|
||||
name="email"
|
||||
icon="envelope"
|
||||
/>
|
||||
<ds-input
|
||||
v-model="form.password"
|
||||
:disabled="pending"
|
||||
:placeholder="$t('login.password')"
|
||||
icon="lock"
|
||||
icon-right="question-circle"
|
||||
name="password"
|
||||
type="password"
|
||||
/>
|
||||
<ds-button
|
||||
:loading="pending"
|
||||
primary
|
||||
full-width
|
||||
name="submit"
|
||||
type="submit"
|
||||
icon="sign-in"
|
||||
>
|
||||
{{ $t('login.login') }}
|
||||
</ds-button>
|
||||
<ds-space margin="x-small">
|
||||
<a
|
||||
href="https://human-connection.org"
|
||||
title="zur Präsentationsseite"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t('login.moreInfo') }}
|
||||
</a>
|
||||
</ds-space>
|
||||
</form>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-card>
|
||||
</ds-container>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LocaleSwitch from '~/components/LocaleSwitch'
|
||||
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LocaleSwitch
|
||||
},
|
||||
layout: 'blank',
|
||||
data() {
|
||||
return {
|
||||
ready: false,
|
||||
form: {
|
||||
email: '',
|
||||
password: ''
|
||||
@ -104,6 +123,13 @@ export default {
|
||||
return this.$store.getters['auth/pending']
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
// NOTE: quick fix for jumping flexbox implementation
|
||||
// will be fixed in a future update of the styleguide
|
||||
this.ready = true
|
||||
}, 50)
|
||||
},
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
try {
|
||||
@ -123,4 +149,12 @@ export default {
|
||||
width: 90%;
|
||||
max-width: 200px;
|
||||
}
|
||||
.login-card {
|
||||
position: relative;
|
||||
}
|
||||
.login-locale-switch {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
left: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -34,7 +34,10 @@
|
||||
<!--<div class="tags">
|
||||
<ds-icon name="compass" /> <ds-tag
|
||||
v-for="category in post.categories"
|
||||
:key="category.id"><ds-icon :name="category.icon" /> {{ category.name }}</ds-tag>
|
||||
:key="category.id"
|
||||
>
|
||||
{{ category.name }}
|
||||
</ds-tag>
|
||||
</div>-->
|
||||
<!-- Tags -->
|
||||
<template v-if="post.tags && post.tags.length">
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
color="soft"
|
||||
size="small"
|
||||
>
|
||||
Mitglied seit {{ user.createdAt | date('MMMM yyyy') }}
|
||||
{{ $t('profile.memberSince') }} {{ user.createdAt | date('MMMM yyyy') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<ds-space
|
||||
@ -52,7 +52,7 @@
|
||||
<ds-flex>
|
||||
<ds-flex-item>
|
||||
<no-ssr>
|
||||
<ds-number label="Folgen">
|
||||
<ds-number :label="$t('profile.following')">
|
||||
<hc-count-to
|
||||
slot="count"
|
||||
:end-val="followedByCount"
|
||||
@ -62,7 +62,7 @@
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<no-ssr>
|
||||
<ds-number label="Folgt">
|
||||
<ds-number :label="$t('profile.followers')">
|
||||
<hc-count-to
|
||||
slot="count"
|
||||
:end-val="Number(user.followingCount) || 0"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-heading tag="h1">
|
||||
Settings
|
||||
{{ $t('settings.name') }}
|
||||
</ds-heading>
|
||||
<ds-flex gutter="small">
|
||||
<ds-flex-item :width="{ base: '200px' }">
|
||||
@ -21,36 +21,36 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
routes: [
|
||||
computed: {
|
||||
routes() {
|
||||
return [
|
||||
{
|
||||
name: 'Your Data',
|
||||
name: this.$t('settings.data.name'),
|
||||
path: `/settings`
|
||||
},
|
||||
{
|
||||
name: 'Password',
|
||||
path: `/settings/password`
|
||||
name: this.$t('settings.security.name'),
|
||||
path: `/settings/security`
|
||||
},
|
||||
{
|
||||
name: 'Invites',
|
||||
name: this.$t('settings.invites.name'),
|
||||
path: `/settings/invites`
|
||||
},
|
||||
{
|
||||
name: 'Data Download',
|
||||
name: this.$t('settings.download.name'),
|
||||
path: `/settings/data-download`
|
||||
},
|
||||
{
|
||||
name: 'Delete Account',
|
||||
name: this.$t('settings.delete.name'),
|
||||
path: `/settings/delete-account`
|
||||
},
|
||||
{
|
||||
name: 'My Organizations',
|
||||
name: this.$t('settings.organizations.name'),
|
||||
path: `/settings/my-organizations`
|
||||
},
|
||||
{
|
||||
name: 'Settings',
|
||||
path: `/settings/settings`
|
||||
name: this.$t('settings.languages.name'),
|
||||
path: `/settings/languages`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
Download my Data...
|
||||
{{ $t('settings.download.name') }}
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
Delete my Account...
|
||||
{{ $t('settings.delete.name') }}
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<ds-card space="small">
|
||||
{{ $t('settings.data.name') }}
|
||||
<ds-input
|
||||
id="name"
|
||||
v-model="form.name"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
Invites...
|
||||
{{ $t('settings.invites.name') }}
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
Settings...
|
||||
{{ $t('settings.languages.name') }}
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
My Organizations...
|
||||
{{ $t('settings.organizations.name') }}
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<ds-space margin="small">
|
||||
Change my Password...
|
||||
{{ $t('settings.security.name') }}
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
@ -1,5 +1,5 @@
|
||||
export default function({ app }) {
|
||||
const backendUrl = process.BACKEND_URL || 'http://localhost:4000'
|
||||
const backendUrl = process.env.BACKEND_URL || 'http://localhost:4000'
|
||||
return {
|
||||
httpEndpoint: process.server ? backendUrl : '/api',
|
||||
httpLinkOptions: {
|
||||
|
||||
102
plugins/i18n.js
Normal file
@ -0,0 +1,102 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import vuexI18n from 'vuex-i18n/dist/vuex-i18n.umd.js'
|
||||
import { debounce, isEmpty, find } from 'lodash'
|
||||
|
||||
/**
|
||||
* TODO: Refactor and simplify browser detection
|
||||
* and implement the user preference logic
|
||||
*/
|
||||
export default ({ app, req, cookie, store }) => {
|
||||
const debug = app.$env.NODE_ENV !== 'production'
|
||||
const key = 'locale'
|
||||
|
||||
const changeHandler = async mutation => {
|
||||
if (process.server) return
|
||||
|
||||
const newLocale = mutation.payload.locale
|
||||
const currentLocale = await app.$cookies.get(key)
|
||||
const isDifferent = newLocale !== currentLocale
|
||||
|
||||
if (!isDifferent) {
|
||||
return
|
||||
}
|
||||
|
||||
app.$cookies.set(key, newLocale)
|
||||
if (!app.$i18n.localeExists(newLocale)) {
|
||||
import(`~/locales/${newLocale}.json`).then(res => {
|
||||
app.$i18n.add(newLocale, res.default)
|
||||
})
|
||||
}
|
||||
|
||||
const user = store.getters['auth/user']
|
||||
const token = store.getters['auth/token']
|
||||
// persist language if it differs from last value
|
||||
if (user && user._id && token) {
|
||||
// TODO: SAVE LOCALE
|
||||
// store.dispatch('usersettings/patch', {
|
||||
// uiLanguage: newLocale
|
||||
// }, { root: true })
|
||||
}
|
||||
}
|
||||
|
||||
// const i18nStore = new Vuex.Store({
|
||||
// strict: debug
|
||||
// })
|
||||
|
||||
Vue.use(vuexI18n.plugin, store, {
|
||||
onTranslationNotFound: function(locale, key) {
|
||||
if (debug) {
|
||||
console.warn(
|
||||
`vuex-i18n :: Key '${key}' not found for locale '${locale}'`
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// register the fallback locales
|
||||
Vue.i18n.add('en', require('~/locales/en.json'))
|
||||
|
||||
let userLocale = 'en'
|
||||
const localeCookie = app.$cookies.get(key)
|
||||
/* const userSettings = store.getters['auth/userSettings']
|
||||
if (userSettings && userSettings.uiLanguage) {
|
||||
// try to get saved user preference
|
||||
userLocale = userSettings.uiLanguage
|
||||
} else */
|
||||
if (!isEmpty(localeCookie)) {
|
||||
userLocale = localeCookie
|
||||
} else {
|
||||
userLocale = process.browser
|
||||
? navigator.language || navigator.userLanguage
|
||||
: req.locale
|
||||
if (userLocale && !isEmpty(userLocale.language)) {
|
||||
userLocale = userLocale.language.substr(0, 2)
|
||||
}
|
||||
}
|
||||
|
||||
const availableLocales = process.env.locales.filter(lang => !!lang.enabled)
|
||||
const locale = find(availableLocales, ['code', userLocale])
|
||||
? userLocale
|
||||
: 'en'
|
||||
|
||||
if (locale !== 'en') {
|
||||
Vue.i18n.add(locale, require(`~/locales/${locale}.json`))
|
||||
}
|
||||
|
||||
// Set the start locale to use
|
||||
Vue.i18n.set(locale)
|
||||
Vue.i18n.fallback('en')
|
||||
|
||||
if (process.browser) {
|
||||
store.subscribe(mutation => {
|
||||
if (mutation.type === 'i18n/SET_LOCALE') {
|
||||
changeHandler(mutation)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
app.$i18n = Vue.i18n
|
||||
|
||||
return store
|
||||
}
|
||||
@ -1,25 +1,44 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
import { en, de } from 'date-fns/locale'
|
||||
import { enUS, de, nl, fr, es } from 'date-fns/locale'
|
||||
import format from 'date-fns/format'
|
||||
import formatRelative from 'date-fns/formatRelative'
|
||||
import addSeconds from 'date-fns/addSeconds'
|
||||
|
||||
import accounting from 'accounting'
|
||||
|
||||
export default ({ app }) => {
|
||||
const locales = {
|
||||
en: enUS,
|
||||
de: de,
|
||||
nl: nl,
|
||||
fr: fr,
|
||||
es: es,
|
||||
pt: es,
|
||||
pl: de
|
||||
}
|
||||
const getLocalizedFormat = () => {
|
||||
let locale = app.$i18n.locale()
|
||||
locale = locales[locale] ? locale : 'en'
|
||||
return locales[locale]
|
||||
}
|
||||
app.$filters = Object.assign(app.$filters || {}, {
|
||||
date: (value, fmt = 'dd. MMM yyyy') => {
|
||||
if (!value) return ''
|
||||
return format(new Date(value), fmt, { locale: de })
|
||||
return format(new Date(value), fmt, {
|
||||
locale: getLocalizedFormat()
|
||||
})
|
||||
},
|
||||
dateTime: (value, fmt = 'dd. MMM yyyy HH:mm') => {
|
||||
if (!value) return ''
|
||||
return format(new Date(value), fmt, { locale: de })
|
||||
return format(new Date(value), fmt, {
|
||||
locale: getLocalizedFormat()
|
||||
})
|
||||
},
|
||||
relativeDateTime: value => {
|
||||
if (!value) return ''
|
||||
return formatRelative(new Date(value), new Date(), { locale: de })
|
||||
return formatRelative(new Date(value), new Date(), {
|
||||
locale: getLocalizedFormat()
|
||||
})
|
||||
},
|
||||
number: (
|
||||
value,
|
||||
|
||||
3
scripts/docker_push.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
docker push humanconnection/nitro-web:latest
|
||||
1
static/img/locale-flags/de.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M16 345a256 256 0 0 0 480 0l-240-22.2L16 345z" fill="#ffda44"/><path d="M256 0A256 256 0 0 0 16 167l240 22.2L496 167A256 256 0 0 0 256 0z"/><path d="M16 167a255.5 255.5 0 0 0 0 178h480a255.4 255.4 0 0 0 0-178H16z" fill="#d80027"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
1
static/img/locale-flags/en.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512"><defs><path id="a" d="M0 512L512 0v512z"/></defs><g fill="none" fill-rule="evenodd"><circle cx="256" cy="256" r="256" fill="#F0F0F0"/><path fill="#D80027" fill-rule="nonzero" d="M327.7 189.2H245V256h15.6l67.2-66.8zm146.7-66.8a257.3 257.3 0 0 0-59-66.7H244.9v66.7h229.5zM127.8 389.6l67.4-66.8H8.8c6.4 23.5 16 46 28.8 66.8h90.2z"/><path fill="#0052B4" fill-rule="nonzero" d="M118.6 40h23.3l-21.7 15.7 8.3 25.6-21.7-15.8-21.7 15.8 7.2-22a257.4 257.4 0 0 0-49.7 55.3h7.5l-13.8 10c-2.2 3.6-4.2 7.2-6.2 11l6.6 20.2-12.3-9c-3.1 6.6-5.9 13.2-8.4 20l7.3 22.3H50L28.4 205l8.3 25.5L15 214.6l-13 9.5C.7 234.7 0 245.3 0 256h256V0c-50.6 0-97.7 14.7-137.4 40zm9.9 190.4l-21.7-15.8-21.7 15.8 8.3-25.5L71.7 189h26.8l8.3-25.5 8.3 25.5h26.8L120.2 205l8.3 25.5zm-8.3-100l8.3 25.4-21.7-15.7-21.7 15.7 8.3-25.5-21.7-15.7h26.8l8.3-25.6 8.3 25.6h26.8l-21.7 15.7zm100.1 100l-21.7-15.8-21.7 15.8 8.3-25.5-21.7-15.8h26.8l8.3-25.5 8.3 25.5h26.8L212 205l8.3 25.5zm-8.3-100l8.3 25.4-21.7-15.7-21.7 15.7 8.3-25.5-21.7-15.7h26.8l8.3-25.6 8.3 25.6h26.8L212 130.3zm0-74.7l8.3 25.6-21.7-15.8L177 81.3l8.3-25.6L163.5 40h26.8l8.3-25.5L207 40h26.8L212 55.7z"/><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><circle cx="256" cy="256" r="256" fill="#F0F0F0"/><path fill="#0052B4" fill-rule="nonzero" d="M503.2 189.2a255 255 0 0 0-44.1-89l-89.1 89h133.2zm-403 269.9a255 255 0 0 0 89 44V370l-89 89zm222.6 44a255 255 0 0 0 89-44l-89-89.1v133.2zM370 322.9l89 89a255 255 0 0 0 44.2-89H370z"/><path fill="#D80027" d="M509.8 222.6H222.4l.2 287.2c22.2 3 44.6 3 66.8 0V289.4h220.4a258.5 258.5 0 0 0 0-66.8z"/><path fill="#D80027" fill-rule="nonzero" d="M322.8 322.8L437 437c5.3-5.2 10.3-10.7 15-16.4l-97.7-97.8h-31.5zm-133.6 0L75 437c5.2 5.3 10.7 10.3 16.4 15l97.8-97.7v-31.5z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
1
static/img/locale-flags/es.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 256c0 31.3 5.6 61.3 16 89l240 22.3L496 345a255.5 255.5 0 0 0 0-178l-240-22.3L16 167a255.5 255.5 0 0 0-16 89z" fill="#ffda44"/><path d="M496 167a256 256 0 0 0-480 0h480zM16 345a256 256 0 0 0 480 0H16z" fill="#d80027"/></svg>
|
||||
|
After Width: | Height: | Size: 297 B |
1
static/img/locale-flags/fr.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="256" fill="#f0f0f0"/><path d="M512 256A256 256 0 0 0 345 16v480a256 256 0 0 0 167-240z" fill="#d80027"/><path d="M0 256a256 256 0 0 0 167 240V16A256 256 0 0 0 0 256z" fill="#0052b4"/></svg>
|
||||
|
After Width: | Height: | Size: 280 B |
1
static/img/locale-flags/it.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="256" fill="#f0f0f0"/><path d="M512 256A256 256 0 0 0 345 16v480a256 256 0 0 0 167-240z" fill="#d80027"/><path d="M0 256a256 256 0 0 0 167 240V16A256 256 0 0 0 0 256z" fill="#6da544"/></svg>
|
||||
|
After Width: | Height: | Size: 280 B |
1
static/img/locale-flags/nl.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="256" fill="#f0f0f0"/><path d="M256 0A256 256 0 0 0 16 167h480A256 256 0 0 0 256 0z" fill="#a2001d"/><path d="M256 512a256 256 0 0 0 240-167H16a256 256 0 0 0 240 167z" fill="#0052b4"/></svg>
|
||||
|
After Width: | Height: | Size: 280 B |
1
static/img/locale-flags/pl.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="256" fill="#f0f0f0"/><path d="M512 256a256 256 0 0 1-512 0" fill="#d80027"/></svg>
|
||||
|
After Width: | Height: | Size: 173 B |
1
static/img/locale-flags/pt.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 256a256 256 0 0 0 167 240l22.2-240L167 16A256 256 0 0 0 0 256z" fill="#6da544"/><path d="M512 256A256 256 0 0 0 167 16v480a256 256 0 0 0 345-240z" fill="#d80027"/><circle cx="167" cy="256" r="89" fill="#ffda44"/><path d="M116.9 211.5V267a50 50 0 1 0 100.1 0v-55.6H117z" fill="#d80027"/><path d="M167 283.8c-9.2 0-16.7-7.5-16.7-16.7V245h33.4V267c0 9.2-7.5 16.7-16.7 16.7z" fill="#f0f0f0"/></svg>
|
||||
|
After Width: | Height: | Size: 468 B |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 180 KiB |
@ -13,11 +13,6 @@ export const mutations = {
|
||||
SET_USER(state, user) {
|
||||
state.user = user || null
|
||||
},
|
||||
SET_USER_SETTINGS(state, userSettings) {
|
||||
// state.user = Object.assign(state.user, {
|
||||
// userSettings: Object.assign(this.getters['auth/userSettings'], userSettings)
|
||||
// })
|
||||
},
|
||||
SET_TOKEN(state, token) {
|
||||
state.token = token || null
|
||||
},
|
||||
@ -27,22 +22,22 @@ export const mutations = {
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
isAuthenticated(state) {
|
||||
return !!state.token
|
||||
},
|
||||
isLoggedIn(state) {
|
||||
return !!(state.user && state.token)
|
||||
},
|
||||
pending(state) {
|
||||
return !!state.pending
|
||||
},
|
||||
isVerified(state) {
|
||||
return !!state.user && state.user.isVerified && !!state.user.name
|
||||
},
|
||||
isAdmin(state) {
|
||||
return !!state.user && state.user.role === 'ADMIN'
|
||||
return !!state.user && state.user.role === 'admin'
|
||||
},
|
||||
isModerator(state) {
|
||||
return (
|
||||
!!state.user &&
|
||||
(state.user.role === 'ADMIN' || state.user.role === 'MODERATOR')
|
||||
(state.user.role === 'admin' || state.user.role === 'moderator')
|
||||
)
|
||||
},
|
||||
user(state) {
|
||||
@ -51,20 +46,6 @@ export const getters = {
|
||||
token(state) {
|
||||
return state.token
|
||||
}
|
||||
// userSettings(state, getters, rootState, rootGetters) {
|
||||
// const userSettings = (state.user && state.user.userSettings) ? state.user.userSettings : {}
|
||||
//
|
||||
// const defaultLanguage = (state.user && state.user.language) ? state.user.language : rootGetters['i18n/locale']
|
||||
// let contentLanguages = !isEmpty(userSettings.contentLanguages) ? userSettings.contentLanguages : []
|
||||
// if (isEmpty(contentLanguages)) {
|
||||
// contentLanguages = userSettings.uiLanguage ? [userSettings.uiLanguage] : [defaultLanguage]
|
||||
// }
|
||||
//
|
||||
// return Object.assign({
|
||||
// uiLanguage: defaultLanguage,
|
||||
// contentLanguages: contentLanguages
|
||||
// }, userSettings)
|
||||
// }
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
@ -114,10 +95,8 @@ export const actions = {
|
||||
})
|
||||
},
|
||||
async login({ commit }, { email, password }) {
|
||||
commit('SET_PENDING', true)
|
||||
try {
|
||||
commit('SET_PENDING', true)
|
||||
commit('SET_USER', null)
|
||||
commit('SET_TOKEN', null)
|
||||
const res = await this.app.apolloProvider.defaultClient
|
||||
.mutate({
|
||||
mutation: gql(`
|
||||
@ -139,22 +118,15 @@ export const actions = {
|
||||
})
|
||||
.then(({ data }) => data && data.login)
|
||||
|
||||
if (res && res.token) {
|
||||
await this.app.$apolloHelpers.onLogin(res.token)
|
||||
commit('SET_TOKEN', res.token)
|
||||
delete res.token
|
||||
commit('SET_USER', res)
|
||||
commit('SET_PENDING', false)
|
||||
return true
|
||||
} else {
|
||||
commit('SET_PENDING', false)
|
||||
throw new Error('THERE IS AN ERROR')
|
||||
}
|
||||
await this.app.$apolloHelpers.onLogin(res.token)
|
||||
commit('SET_TOKEN', res.token)
|
||||
const userData = Object.assign({}, res)
|
||||
delete userData.token
|
||||
commit('SET_USER', userData)
|
||||
} catch (err) {
|
||||
commit('SET_USER', null)
|
||||
commit('SET_TOKEN', null)
|
||||
commit('SET_PENDING', false)
|
||||
throw new Error(err)
|
||||
} finally {
|
||||
commit('SET_PENDING', false)
|
||||
}
|
||||
},
|
||||
async logout({ commit }) {
|
||||
|
||||
160
store/auth.test.js
Normal file
@ -0,0 +1,160 @@
|
||||
import { getters, mutations, actions } from './auth.js'
|
||||
|
||||
let state
|
||||
let commit
|
||||
|
||||
const token =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InUzIiwic2x1ZyI6Implbm55LXJvc3RvY2siLCJuYW1lIjoiSmVubnkgUm9zdG9jayIsImF2YXRhciI6Imh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS91aWZhY2VzL2ZhY2VzL3R3aXR0ZXIvbXV0dV9rcmlzaC8xMjguanBnIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUub3JnIiwicm9sZSI6InVzZXIiLCJpYXQiOjE1NDUxNDQ2ODgsImV4cCI6MTYzMTU0NDY4OCwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo0MDAwIiwic3ViIjoidTMifQ.s5_JeQN9TaUPfymAXPOpbMAwhmTIg9cnOvNEcj4z75k'
|
||||
const successfulLoginResponse = {
|
||||
data: {
|
||||
login: {
|
||||
id: 'u3',
|
||||
name: 'Jenny Rostock',
|
||||
slug: 'jenny-rostock',
|
||||
email: 'user@example.org',
|
||||
avatar:
|
||||
'https://s3.amazonaws.com/uifaces/faces/twitter/mutu_krish/128.jpg',
|
||||
role: 'user',
|
||||
token
|
||||
}
|
||||
}
|
||||
}
|
||||
const incorrectPasswordResponse = {
|
||||
data: {
|
||||
login: null
|
||||
},
|
||||
errors: [
|
||||
{
|
||||
message: 'Incorrect password.',
|
||||
locations: [
|
||||
{
|
||||
line: 2,
|
||||
column: 3
|
||||
}
|
||||
],
|
||||
path: ['login']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
commit = jest.fn()
|
||||
})
|
||||
|
||||
describe('getters', () => {
|
||||
describe('isAuthenticated', () => {
|
||||
describe('given JWT Bearer token', () => {
|
||||
test('true', () => {
|
||||
state = { token }
|
||||
expect(getters.isAuthenticated(state)).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('actions', () => {
|
||||
let action
|
||||
|
||||
describe('login', () => {
|
||||
describe('given valid credentials and a successful response', () => {
|
||||
beforeEach(async () => {
|
||||
const response = Object.assign({}, successfulLoginResponse)
|
||||
const mutate = jest.fn(() => Promise.resolve(response))
|
||||
const onLogin = jest.fn(() => Promise.resolve())
|
||||
const module = {
|
||||
app: {
|
||||
apolloProvider: { defaultClient: { mutate } },
|
||||
$apolloHelpers: { onLogin }
|
||||
}
|
||||
}
|
||||
action = actions.login.bind(module)
|
||||
await action(
|
||||
{ commit },
|
||||
{ email: 'user@example.org', password: '1234' }
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
action = null
|
||||
})
|
||||
|
||||
it('saves the JWT Bearer token', () => {
|
||||
expect(commit.mock.calls).toEqual(
|
||||
expect.arrayContaining([['SET_TOKEN', token]])
|
||||
)
|
||||
})
|
||||
|
||||
it('saves user data without token', () => {
|
||||
expect(commit.mock.calls).toEqual(
|
||||
expect.arrayContaining([
|
||||
[
|
||||
'SET_USER',
|
||||
{
|
||||
id: 'u3',
|
||||
name: 'Jenny Rostock',
|
||||
slug: 'jenny-rostock',
|
||||
email: 'user@example.org',
|
||||
avatar:
|
||||
'https://s3.amazonaws.com/uifaces/faces/twitter/mutu_krish/128.jpg',
|
||||
role: 'user'
|
||||
}
|
||||
]
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it('saves pending flags in order', () => {
|
||||
expect(commit.mock.calls).toEqual(
|
||||
expect.arrayContaining([
|
||||
['SET_PENDING', true],
|
||||
['SET_PENDING', false]
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given invalid credentials and incorrect password response', () => {
|
||||
let onLogin
|
||||
let mutate
|
||||
|
||||
beforeEach(() => {
|
||||
mutate = jest.fn(() => Promise.reject('This error is expected.'))
|
||||
onLogin = jest.fn(() => Promise.resolve())
|
||||
const module = {
|
||||
app: {
|
||||
apolloProvider: { defaultClient: { mutate } },
|
||||
$apolloHelpers: { onLogin }
|
||||
}
|
||||
}
|
||||
action = actions.login.bind(module)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
action = null
|
||||
})
|
||||
|
||||
it('populates error messages', async () => {
|
||||
expect(
|
||||
action({ commit }, { email: 'user@example.org', password: 'wrong' })
|
||||
).rejects.toThrowError('This error is expected.')
|
||||
expect(mutate).toHaveBeenCalled()
|
||||
expect(onLogin).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('saves pending flags in order', async () => {
|
||||
try {
|
||||
await action(
|
||||
{ commit },
|
||||
{ email: 'user@example.org', password: 'wrong' }
|
||||
)
|
||||
} catch (err) {} // ignore
|
||||
expect(commit.mock.calls).toEqual(
|
||||
expect.arrayContaining([
|
||||
['SET_PENDING', true],
|
||||
['SET_PENDING', false]
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -20,7 +20,8 @@
|
||||
<tr>
|
||||
<ds-table-head-col
|
||||
v-for="header in headers"
|
||||
:key="header.key">
|
||||
:key="header.key"
|
||||
:align="align(header.key)">
|
||||
{{ header.label }}
|
||||
</ds-table-head-col>
|
||||
</tr>
|
||||
@ -31,7 +32,8 @@
|
||||
:key="row.key || index">
|
||||
<ds-table-col
|
||||
v-for="col in row"
|
||||
:key="col.key">
|
||||
:key="col.key"
|
||||
:align="align(col.key)">
|
||||
<!-- @slot Slots are named by fields -->
|
||||
<slot
|
||||
:name="col.key"
|
||||
@ -152,6 +154,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
align(colKey) {
|
||||
return this.fields && this.fields[colKey] ? this.fields[colKey].align : null
|
||||
},
|
||||
parseLabel(label) {
|
||||
return startCase(label)
|
||||
}
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<td class="ds-table-col">
|
||||
<!-- eslint-disable -->
|
||||
<td
|
||||
class="ds-table-col"
|
||||
:class="[
|
||||
align && `ds-table-col-${align}`
|
||||
]">
|
||||
<slot/>
|
||||
</td>
|
||||
</template>
|
||||
@ -25,6 +30,17 @@ export default {
|
||||
width: {
|
||||
type: [String, Number, Object],
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* The column align
|
||||
* `left, center, right`
|
||||
*/
|
||||
align: {
|
||||
type: String,
|
||||
default: null,
|
||||
validator: value => {
|
||||
return value.match(/(left|center|right)/)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {}
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<th class="ds-table-head-col">
|
||||
<th
|
||||
class="ds-table-head-col"
|
||||
:class="[
|
||||
align && `ds-table-head-col-${align}`
|
||||
]">
|
||||
<slot>
|
||||
{{ label }}
|
||||
</slot>
|
||||
@ -36,6 +40,17 @@ export default {
|
||||
width: {
|
||||
type: [String, Number, Object],
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* The column align
|
||||
* `left, center, right`
|
||||
*/
|
||||
align: {
|
||||
type: String,
|
||||
default: null,
|
||||
validator: value => {
|
||||
return value.match(/(left|center|right)/)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {}
|
||||
|
||||
@ -100,7 +100,8 @@ The value can be a string representing the fields label or an object with option
|
||||
name: 'Hero',
|
||||
type: {
|
||||
label: 'Job',
|
||||
width: '300px'
|
||||
width: '300px',
|
||||
align: 'right'
|
||||
}
|
||||
},
|
||||
tableData: [
|
||||
@ -191,4 +192,4 @@ Via scoped slots you have access to the columns `row`, `index` and `col`.
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
```
|
||||
|
||||
@ -42,3 +42,19 @@
|
||||
padding-bottom: $space-x-small;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-table-col,
|
||||
.ds-table-head-col {
|
||||
&.ds-table-col-left,
|
||||
&.ds-table-head-col-left {
|
||||
text-align: left;
|
||||
}
|
||||
&.ds-table-col-center,
|
||||
&.ds-table-head-col-center {
|
||||
text-align: center;
|
||||
}
|
||||
&.ds-table-col-right,
|
||||
&.ds-table-head-col-right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +46,10 @@ export default {
|
||||
default: null
|
||||
},
|
||||
|
||||
align: {
|
||||
/**
|
||||
* Center content vertacally and horizontally
|
||||
*/
|
||||
center: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
@ -44,6 +44,7 @@ ul.ds-menu-list {
|
||||
text-decoration: none;
|
||||
padding: $space-x-small $space-small;
|
||||
transition: color $duration-short $ease-out;
|
||||
border-left: 2px solid transparent;
|
||||
|
||||
&.router-link-active,
|
||||
&.nuxt-link-active {
|
||||
@ -56,8 +57,9 @@ ul.ds-menu-list {
|
||||
|
||||
&.router-link-exact-active,
|
||||
&.nuxt-link-exact-active {
|
||||
color: $text-color-link-active;
|
||||
background-color: $background-color-soft;
|
||||
color: $text-color-link;
|
||||
// background-color: $background-color-soft;
|
||||
border-left: 2px solid $color-primary;
|
||||
}
|
||||
|
||||
.ds-menu-item-inverse & {
|
||||
|
||||