mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge pull request #3397 from gradido/montreal_user_location
feat(frontend): user location with vue3 in cooperation with montreal
This commit is contained in:
commit
16bdc78df4
23
.github/workflows/test_e2e.yml
vendored
23
.github/workflows/test_e2e.yml
vendored
@ -5,7 +5,7 @@ on: push
|
||||
jobs:
|
||||
end-to-end-tests:
|
||||
name: End-to-End Tests
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
@ -13,19 +13,6 @@ jobs:
|
||||
- name: Boot up test system | docker-compose mariadb
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach mariadb
|
||||
|
||||
- name: Boot up test system | docker-compose database
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
|
||||
|
||||
- name: Boot up test system | docker-compose backend
|
||||
run: |
|
||||
cd backend
|
||||
cp .env.test_e2e .env
|
||||
cd ..
|
||||
docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend
|
||||
|
||||
- name: Boot up test system | docker-compose frontends
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps frontend admin nginx
|
||||
|
||||
- name: Sleep for 10 seconds
|
||||
run: sleep 10s
|
||||
|
||||
@ -37,8 +24,12 @@ jobs:
|
||||
cd ../backend
|
||||
yarn && yarn seed
|
||||
|
||||
- name: Boot up test system | docker-compose mailserver
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver
|
||||
- name: Boot up test system | docker-compose backend, frontend, admin, nginx, mailserver
|
||||
run: |
|
||||
cd backend
|
||||
cp .env.test_e2e .env
|
||||
cd ..
|
||||
docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend frontend admin nginx mailserver
|
||||
|
||||
- name: End-to-end tests | prepare
|
||||
run: |
|
||||
|
||||
1
admin/.gitignore
vendored
1
admin/.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
node_modules/
|
||||
build/
|
||||
.cache/
|
||||
.yarn/install-state.gz
|
||||
|
||||
/.env
|
||||
/.env.bak
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
"author": "Moriz Wahl",
|
||||
"version": "2.4.1",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"start": "node run/server.js",
|
||||
"dev": "vite",
|
||||
@ -37,7 +36,7 @@
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-vue": "^2.0.2",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-vue-next": "^0.23.2",
|
||||
"bootstrap-vue-next": "0.26.8",
|
||||
"date-fns": "^2.29.3",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"express": "^4.17.1",
|
||||
@ -50,7 +49,7 @@
|
||||
"sass": "^1.77.8",
|
||||
"vite": "3.2.10",
|
||||
"vite-plugin-commonjs": "^0.10.1",
|
||||
"vue": "3.4.31",
|
||||
"vue": "3.5.13",
|
||||
"vue-apollo": "3.1.2",
|
||||
"vue-i18n": "9.13.1",
|
||||
"vue-router": "4.4.0",
|
||||
|
||||
7
admin/prepare-and-build.sh
Executable file
7
admin/prepare-and-build.sh
Executable file
@ -0,0 +1,7 @@
|
||||
# TODO this is the quick&dirty solution for the openssl security topic, please see https://stackoverflow.com/questions/69692842/error-message-error0308010cdigital-envelope-routinesunsupported
|
||||
$env:NODE_OPTIONS = "--openssl-legacy-provider"
|
||||
|
||||
nvm use
|
||||
yarn cache clean
|
||||
yarn install
|
||||
yarn build
|
||||
@ -1,11 +1,12 @@
|
||||
// Imports
|
||||
import CONFIG from '../src/config'
|
||||
|
||||
const express = require('express')
|
||||
const path = require('path')
|
||||
|
||||
// Host & Port
|
||||
const hostname = '127.0.0.1'
|
||||
const port = import.meta.env.PORT || 8080
|
||||
|
||||
const hostname = CONFIG.ADMIN_MODULE_HOST // '127.0.0.1'
|
||||
const port = CONFIG.ADMIN_MODULE_PORT // process.env.PORT || 8080
|
||||
// Express Server
|
||||
const app = express()
|
||||
// Serve files
|
||||
|
||||
@ -125,6 +125,7 @@ import { createContributionLink } from '@/graphql/createContributionLink.js'
|
||||
import { updateContributionLink } from '@/graphql/updateContributionLink.js'
|
||||
import { useAppToast } from '@/composables/useToast'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDateFormatter } from '@/composables/useDateFormatter'
|
||||
|
||||
const props = defineProps({
|
||||
contributionLinkData: {
|
||||
@ -138,6 +139,8 @@ const emit = defineEmits(['get-contribution-links', 'close-contribution-form'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { formatDateFromDateTime } = useDateFormatter()
|
||||
|
||||
const contributionLinkForm = ref(null)
|
||||
|
||||
const form = ref({
|
||||
@ -201,11 +204,6 @@ const onSubmit = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const formatDateFromDateTime = (datetimeString) => {
|
||||
if (!datetimeString || !datetimeString?.includes('T')) return datetimeString
|
||||
return datetimeString.split('T')[0]
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
form.value = { validFrom: null, validTo: null }
|
||||
}
|
||||
|
||||
@ -8,8 +8,10 @@
|
||||
</BFormCheckbox>
|
||||
</BFormGroup>
|
||||
<BFormGroup v-if="showResubmissionDate">
|
||||
<BFormInput v-model="resubmissionDate" type="date" :min="now"></BFormInput>
|
||||
<time-picker v-model="resubmissionTime"></time-picker>
|
||||
<div class="d-flex my-2">
|
||||
<BFormInput v-model="resubmissionDate" type="date" :min="now" class="w-25 me-2" />
|
||||
<time-picker v-model="resubmissionTime" />
|
||||
</div>
|
||||
</BFormGroup>
|
||||
<BTabs v-model="tabindex" content-class="mt-3" data-test="message-type-tabs">
|
||||
<BTab active>
|
||||
@ -24,7 +26,7 @@
|
||||
v-model="form.text"
|
||||
:placeholder="$t('contributionLink.memo')"
|
||||
rows="3"
|
||||
></BFormTextarea>
|
||||
/>
|
||||
</BTab>
|
||||
<BTab>
|
||||
<template #title>
|
||||
@ -38,7 +40,7 @@
|
||||
v-model="form.text"
|
||||
:placeholder="$t('moderator.notice')"
|
||||
rows="3"
|
||||
></BFormTextarea>
|
||||
/>
|
||||
</BTab>
|
||||
<BTab>
|
||||
<template #title>
|
||||
@ -52,7 +54,7 @@
|
||||
v-model="form.memo"
|
||||
:placeholder="$t('contributionLink.memo')"
|
||||
rows="3"
|
||||
></BFormTextarea>
|
||||
/>
|
||||
</BTab>
|
||||
</BTabs>
|
||||
<BRow class="mt-4 mb-6">
|
||||
@ -85,6 +87,7 @@ import TimePicker from '@/components/input/TimePicker'
|
||||
import { adminCreateContributionMessage } from '@/graphql/adminCreateContributionMessage'
|
||||
import { adminUpdateContribution } from '@/graphql/adminUpdateContribution'
|
||||
import { useAppToast } from '@/composables/useToast'
|
||||
import { useDateFormatter } from '@/composables/useDateFormatter'
|
||||
|
||||
const props = defineProps({
|
||||
contributionId: {
|
||||
@ -115,6 +118,7 @@ const emit = defineEmits([
|
||||
|
||||
const { t } = useI18n()
|
||||
const { toastError, toastSuccess } = useAppToast()
|
||||
const { formatDateFromDateTime } = useDateFormatter()
|
||||
|
||||
const form = ref({
|
||||
text: '',
|
||||
@ -125,7 +129,7 @@ const loading = ref(false)
|
||||
const localInputResubmissionDate = props.inputResubmissionDate
|
||||
? new Date(props.inputResubmissionDate)
|
||||
: null
|
||||
const resubmissionDate = ref(localInputResubmissionDate)
|
||||
const resubmissionDate = ref(formatDateFromDateTime(props.inputResubmissionDate))
|
||||
const resubmissionTime = ref(
|
||||
localInputResubmissionDate
|
||||
? localInputResubmissionDate.toLocaleTimeString('de-DE', {
|
||||
@ -141,14 +145,20 @@ const messageType = {
|
||||
MODERATOR: 'MODERATOR',
|
||||
}
|
||||
|
||||
const disabled = computed(() => {
|
||||
return (
|
||||
(tabindex.value === 0 && form.value.text === '') ||
|
||||
const isTextTabValid = computed(() => form.value.text !== '')
|
||||
|
||||
const isMemoTabValid = computed(() => form.value.memo.length >= 5)
|
||||
|
||||
const disabled = computed(
|
||||
() =>
|
||||
loading.value ||
|
||||
(tabindex.value === 1 && form.value.memo.length < 5) ||
|
||||
(showResubmissionDate.value && !resubmissionDate.value)
|
||||
)
|
||||
})
|
||||
(!(showResubmissionDate.value && resubmissionDate.value) &&
|
||||
([0, 1].includes(tabindex.value)
|
||||
? !isTextTabValid.value
|
||||
: tabindex.value === 2
|
||||
? !isMemoTabValid.value
|
||||
: false)),
|
||||
)
|
||||
|
||||
const now = computed(() => new Date())
|
||||
|
||||
|
||||
@ -19,8 +19,8 @@ describe('TimePicker', () => {
|
||||
expect(wrapper.vm.timeValue).toBe('23:45')
|
||||
|
||||
// Check if update:modelValue event is emitted with updated value
|
||||
expect(wrapper.emitted('input')).toBeTruthy()
|
||||
expect(wrapper.emitted('input')[0]).toEqual(['23:45'])
|
||||
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
|
||||
expect(wrapper.emitted('update:modelValue')[0]).toEqual(['23:45'])
|
||||
})
|
||||
|
||||
it('validates and corrects time format on blur', async () => {
|
||||
@ -30,8 +30,8 @@ describe('TimePicker', () => {
|
||||
|
||||
// Simulate user input
|
||||
await input.setValue('99:99')
|
||||
expect(wrapper.emitted('input')).toBeTruthy()
|
||||
expect(wrapper.emitted('input')[0]).toEqual(['99:99'])
|
||||
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
|
||||
expect(wrapper.emitted('update:modelValue')[0]).toEqual(['99:99'])
|
||||
|
||||
// Trigger blur event
|
||||
await input.trigger('blur')
|
||||
@ -40,8 +40,8 @@ describe('TimePicker', () => {
|
||||
expect(wrapper.vm.timeValue).toBe('23:59') // Maximum allowed value for hours and minutes
|
||||
|
||||
// Check if update:modelValue event is emitted with corrected value
|
||||
expect(wrapper.emitted('input')).toBeTruthy()
|
||||
expect(wrapper.emitted('input')[1]).toEqual(['23:59'])
|
||||
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
|
||||
expect(wrapper.emitted('update:modelValue')[1]).toEqual(['23:59'])
|
||||
})
|
||||
|
||||
it('checks handling of empty input', async () => {
|
||||
@ -58,7 +58,7 @@ describe('TimePicker', () => {
|
||||
expect(wrapper.vm.timeValue).toBe('00:00')
|
||||
|
||||
// Check if update:modelValue event is emitted with default value
|
||||
expect(wrapper.emitted('input')).toBeTruthy()
|
||||
expect(wrapper.emitted('input')[1]).toEqual(['00:00'])
|
||||
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
|
||||
expect(wrapper.emitted('update:modelValue')[1]).toEqual(['00:00'])
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
<div>
|
||||
<input
|
||||
v-model="timeValue"
|
||||
class="timer-input"
|
||||
type="text"
|
||||
placeholder="hh:mm"
|
||||
@input="updateValues"
|
||||
@ -15,15 +16,15 @@ export default {
|
||||
// Code written from chatGPT 3.5
|
||||
name: 'TimePicker',
|
||||
props: {
|
||||
value: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '00:00',
|
||||
},
|
||||
},
|
||||
emits: ['input'],
|
||||
emits: ['update:modelValue'],
|
||||
data() {
|
||||
return {
|
||||
timeValue: this.value,
|
||||
timeValue: this.modelValue,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -31,7 +32,7 @@ export default {
|
||||
// Allow only numbers and ":"
|
||||
const inputValue = event.target.value.replace(/[^0-9:]/g, '')
|
||||
this.timeValue = inputValue
|
||||
this.$emit('input', inputValue)
|
||||
this.$emit('update:modelValue', inputValue)
|
||||
},
|
||||
validateAndCorrect() {
|
||||
let [hours, minutes] = this.timeValue.split(':')
|
||||
@ -42,8 +43,16 @@ export default {
|
||||
|
||||
// Update the value with correct format
|
||||
this.timeValue = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`
|
||||
this.$emit('input', this.timeValue)
|
||||
this.$emit('update:modelValue', this.timeValue)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.timer-input {
|
||||
border: 1px solid rgb(222 226 230);
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
10
admin/src/composables/useDateFormatter.js
Normal file
10
admin/src/composables/useDateFormatter.js
Normal file
@ -0,0 +1,10 @@
|
||||
export const useDateFormatter = () => {
|
||||
const formatDateFromDateTime = (datetimeString) => {
|
||||
if (!datetimeString || !datetimeString?.includes('T')) return datetimeString
|
||||
return datetimeString.split('T')[0]
|
||||
}
|
||||
|
||||
return {
|
||||
formatDateFromDateTime,
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useToast } from 'bootstrap-vue-next'
|
||||
import { useToastController } from 'bootstrap-vue-next'
|
||||
|
||||
export function useAppToast() {
|
||||
const { t } = useI18n()
|
||||
const { show } = useToast()
|
||||
const { show } = useToastController()
|
||||
const toastSuccess = (message) => {
|
||||
toast(message, {
|
||||
title: t('success'),
|
||||
|
||||
@ -7,35 +7,50 @@ const pkg = require('../../package')
|
||||
const constants = {
|
||||
CONFIG_VERSION: {
|
||||
DEFAULT: 'DEFAULT',
|
||||
EXPECTED: 'v2.2024-01-04',
|
||||
EXPECTED: 'v3.2024-08-06',
|
||||
CURRENT: '',
|
||||
},
|
||||
}
|
||||
|
||||
const version = {
|
||||
ADMIN_MODULE_PROTOCOL: process.env.ADMIN_MODULE_PROTOCOL ?? 'http',
|
||||
ADMIN_MODULE_HOST: process.env.ADMIN_MODULE_HOST ?? '0.0.0.0',
|
||||
ADMIN_MODULE_PORT: process.env.ADMIN_MODULE_PORT ?? '8080',
|
||||
APP_VERSION: pkg.version,
|
||||
BUILD_COMMIT: process.env.BUILD_COMMIT ?? null,
|
||||
// self reference of `version.BUILD_COMMIT` is not possible at this point, hence the duplicate code
|
||||
BUILD_COMMIT_SHORT: (process.env.BUILD_COMMIT ?? '0000000').slice(0, 7),
|
||||
PORT: process.env.PORT ?? 8080,
|
||||
}
|
||||
|
||||
let ADMIN_MODULE_URL
|
||||
// in case of hosting the admin module with a nodejs-instance
|
||||
if (process.env.ADMIN_HOSTING === 'nodejs') {
|
||||
ADMIN_MODULE_URL =
|
||||
version.ADMIN_MODULE_PROTOCOL +
|
||||
'://' +
|
||||
version.ADMIN_MODULE_HOST +
|
||||
':' +
|
||||
version.ADMIN_MODULE_PORT
|
||||
} else {
|
||||
// in case of hosting the admin module with a nginx
|
||||
ADMIN_MODULE_URL = version.ADMIN_MODULE_PROTOCOL + '://' + version.ADMIN_MODULE_HOST
|
||||
}
|
||||
|
||||
const environment = {
|
||||
NODE_ENV: import.meta.env.NODE_ENV,
|
||||
DEBUG: import.meta.env.NODE_ENV !== 'production' ?? false,
|
||||
PRODUCTION: import.meta.env.NODE_ENV === 'production' ?? false,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
DEBUG: process.env.NODE_ENV !== 'production' ?? false,
|
||||
PRODUCTION: process.env.NODE_ENV === 'production' ?? false,
|
||||
}
|
||||
|
||||
const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? undefined
|
||||
const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http'
|
||||
const COMMUNITY_URL =
|
||||
COMMUNITY_HOST && URL_PROTOCOL ? URL_PROTOCOL + '://' + COMMUNITY_HOST : undefined
|
||||
// const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? undefined
|
||||
// const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http'
|
||||
// const COMMUNITY_URL =
|
||||
// COMMUNITY_HOST && URL_PROTOCOL ? URL_PROTOCOL + '://' + COMMUNITY_HOST : undefined
|
||||
const COMMUNITY_URL = process.env.COMMUNITY_URL ?? ADMIN_MODULE_URL
|
||||
const WALLET_URL = process.env.WALLET_URL ?? COMMUNITY_URL ?? 'http://localhost'
|
||||
|
||||
const endpoints = {
|
||||
GRAPHQL_URL:
|
||||
(process.env.GRAPHQL_URL ?? COMMUNITY_URL ?? 'http://localhost:4000') +
|
||||
process.env.GRAPHQL_PATH ?? '/graphql',
|
||||
GRAPHQL_URI: process.env.GRAPHQL_URL ?? COMMUNITY_URL + (process.env.GRAPHQL_PATH ?? '/graphql'),
|
||||
WALLET_AUTH_URL: WALLET_URL + (process.env.WALLET_AUTH_PATH ?? '/authenticate?token={token}'),
|
||||
WALLET_LOGIN_URL: WALLET_URL + (process.env.WALLET_LOGIN_PATH ?? '/login'),
|
||||
}
|
||||
@ -64,4 +79,4 @@ const CONFIG = {
|
||||
...debug,
|
||||
}
|
||||
|
||||
export default CONFIG
|
||||
module.exports = CONFIG
|
||||
|
||||
@ -7,13 +7,19 @@ import IconsResolve from 'unplugin-icons/resolver'
|
||||
import { BootstrapVueNextResolver } from 'bootstrap-vue-next'
|
||||
import EnvironmentPlugin from 'vite-plugin-environment'
|
||||
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
dotenv.config() // load env vars from .env
|
||||
|
||||
const CONFIG = require('./src/config')
|
||||
|
||||
const path = require('path')
|
||||
|
||||
export default defineConfig({
|
||||
base: '/admin/',
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 8080,
|
||||
host: CONFIG.ADMIN_MODULE_HOST, // '0.0.0.0',
|
||||
port: CONFIG.ADMIN_MODULE_PORT, // 8080,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
@ -42,21 +48,22 @@ export default defineConfig({
|
||||
}),
|
||||
EnvironmentPlugin({
|
||||
BUILD_COMMIT: null,
|
||||
PORT: null,
|
||||
COMMUNITY_HOST: null,
|
||||
URL_PROTOCOL: null,
|
||||
WALLET_URL: null,
|
||||
GRAPHQL_URL: null,
|
||||
GRAPHQL_PATH: null,
|
||||
WALLET_AUTH_PATH: null,
|
||||
WALLET_LOGIN_PATH: null,
|
||||
DEBUG_DISABLE_AUTH: null,
|
||||
CONFIG_VERSION: null,
|
||||
PORT: CONFIG.ADMIN_MODULE_PORT, // null,
|
||||
COMMUNITY_HOST: CONFIG.ADMIN_MODULE_HOST, // null,
|
||||
URL_PROTOCOL: CONFIG.ADMIN_MODULE_PROTOCOL, // null,
|
||||
WALLET_URL: CONFIG.WALLET_AUTH_URL, // null,
|
||||
GRAPHQL_URL: CONFIG.GRAPHQL_URI, // null,
|
||||
GRAPHQL_PATH: process.env.GRAPHQL_PATH ?? '/graphql', // null,
|
||||
WALLET_AUTH_PATH: CONFIG.WALLET_AUTH_URL, // null,
|
||||
WALLET_LOGIN_PATH: CONFIG.WALLET_LOGIN_URL, // null,
|
||||
DEBUG_DISABLE_AUTH: CONFIG.DEBUG_DISABLE_AUTH, // null,
|
||||
CONFIG_VERSION: CONFIG.CONFIG_VERSION, // null,
|
||||
}),
|
||||
commonjs(),
|
||||
],
|
||||
build: {
|
||||
outDir: path.resolve(__dirname, './build'),
|
||||
chunkSizeWarningLimit: 1600,
|
||||
},
|
||||
publicDir: '/admin',
|
||||
})
|
||||
|
||||
@ -7,7 +7,7 @@ const CONFIG = require('./src/config')
|
||||
// vue.config.js
|
||||
module.exports = {
|
||||
devServer: {
|
||||
port: CONFIG.PORT,
|
||||
port: CONFIG.ADMIN_MODULE_PORT,
|
||||
},
|
||||
pluginOptions: {
|
||||
i18n: {
|
||||
|
||||
4011
admin/yarn.lock
4011
admin/yarn.lock
File diff suppressed because it is too large
Load Diff
78
backend/.env.org
Normal file
78
backend/.env.org
Normal file
@ -0,0 +1,78 @@
|
||||
# Server
|
||||
PORT=4000
|
||||
JWT_SECRET=secret123
|
||||
JWT_EXPIRES_IN=10m
|
||||
GRAPHIQL=false
|
||||
GDT_API_URL=https://gdt.gradido.net
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=
|
||||
DB_DATABASE=gradido_community
|
||||
TYPEORM_LOGGING_RELATIVE_PATH=typeorm.backend.log
|
||||
|
||||
# Klicktipp
|
||||
KLICKTIPP=false
|
||||
KLICKTTIPP_API_URL=https://api.klicktipp.com
|
||||
KLICKTIPP_USER=gradido_test
|
||||
KLICKTIPP_PASSWORD=secret321
|
||||
KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
|
||||
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
|
||||
|
||||
# DltConnector
|
||||
DLT_CONNECTOR=true
|
||||
DLT_CONNECTOR_URL=http://localhost:6010
|
||||
|
||||
# Community
|
||||
COMMUNITY_NAME=Gradido Entwicklung
|
||||
COMMUNITY_URL=http://localhost
|
||||
COMMUNITY_REGISTER_PATH=/register
|
||||
COMMUNITY_REDEEM_PATH=/redeem/{code}
|
||||
COMMUNITY_REDEEM_CONTRIBUTION_PATH=/redeem/CL-{code}
|
||||
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
|
||||
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
|
||||
|
||||
# Login Server
|
||||
LOGIN_APP_SECRET=21ffbbc616fe
|
||||
LOGIN_SERVER_KEY=a51ef8ac7ef1abf162fb7a65261acd7a
|
||||
|
||||
# EMail
|
||||
EMAIL=false
|
||||
EMAIL_TEST_MODUS=false
|
||||
EMAIL_TEST_RECEIVER=stage1@gradido.net
|
||||
EMAIL_USERNAME=gradido_email
|
||||
EMAIL_SENDER=info@gradido.net
|
||||
EMAIL_PASSWORD=xxx
|
||||
EMAIL_SMTP_URL=gmail.com
|
||||
EMAIL_SMTP_PORT=587
|
||||
EMAIL_LINK_VERIFICATION_PATH=/checkEmail/{optin}{code}
|
||||
EMAIL_LINK_SETPASSWORD_PATH=/reset-password/{optin}
|
||||
EMAIL_LINK_FORGOTPASSWORD_PATH=/forgot-password
|
||||
EMAIL_LINK_OVERVIEW_PATH=/overview
|
||||
EMAIL_CODE_VALID_TIME=1440
|
||||
EMAIL_CODE_REQUEST_TIME=10
|
||||
|
||||
# Webhook
|
||||
WEBHOOK_ELOPAGE_SECRET=secret
|
||||
|
||||
# SET LOG LEVEL AS NEEDED IN YOUR .ENV
|
||||
# POSSIBLE VALUES: all | trace | debug | info | warn | error | fatal
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# Federation
|
||||
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
|
||||
FEDERATION_XCOM_SENDCOINS_ENABLED=false
|
||||
|
||||
# GMS
|
||||
# GMS_ACTIVE=true
|
||||
# Coordinates of Illuminz test instance
|
||||
#GMS_API_URL=http://54.176.169.179:3071
|
||||
GMS_API_URL=http://localhost:4044
|
||||
GMS_DASHBOARD_URL=http://localhost:8080
|
||||
|
||||
# HUMHUB
|
||||
HUMHUB_ACTIVE=true
|
||||
HUMHUB_API_URL=https://community-test.gradido.net
|
||||
HUMHUB_JWT_KEY=GwdkIKi-rkRS0mXC4Cg3MYc3ktZh89VFmntDpNKET_dUfcIdjL_957F3nCv3brNtDfbbV81NViKaktUsfExrkH
|
||||
@ -28,8 +28,8 @@ export class GmsUser {
|
||||
status: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
firstName: string | undefined
|
||||
lastName: string | undefined
|
||||
firstName: string | null | undefined
|
||||
lastName: string | null | undefined
|
||||
alias: string | undefined
|
||||
type: number
|
||||
address: string | undefined
|
||||
@ -48,9 +48,19 @@ export class GmsUser {
|
||||
) {
|
||||
return user.alias
|
||||
}
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
((!user.alias && user.gmsPublishName === PublishNameType.PUBLISH_NAME_ALIAS_OR_INITALS) ||
|
||||
user.gmsPublishName === PublishNameType.PUBLISH_NAME_INITIALS)
|
||||
) {
|
||||
return (
|
||||
this.firstUpperCaseSecondLowerCase(user.firstName) +
|
||||
this.firstUpperCaseSecondLowerCase(user.lastName)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private getGmsFirstName(user: dbUser): string | undefined {
|
||||
private getGmsFirstName(user: dbUser): string | null | undefined {
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
(user.gmsPublishName === PublishNameType.PUBLISH_NAME_FIRST ||
|
||||
@ -64,22 +74,30 @@ export class GmsUser {
|
||||
((!user.alias && user.gmsPublishName === PublishNameType.PUBLISH_NAME_ALIAS_OR_INITALS) ||
|
||||
user.gmsPublishName === PublishNameType.PUBLISH_NAME_INITIALS)
|
||||
) {
|
||||
return user.firstName.substring(0, 1)
|
||||
// return this.firstUpperCaseSecondLowerCase(user.firstName)
|
||||
return null // cause to delete firstname in gms
|
||||
}
|
||||
}
|
||||
|
||||
private getGmsLastName(user: dbUser): string | undefined {
|
||||
private getGmsLastName(user: dbUser): string | null | undefined {
|
||||
if (user.gmsAllowed && user.gmsPublishName === PublishNameType.PUBLISH_NAME_FULL) {
|
||||
return user.lastName
|
||||
}
|
||||
if (user.gmsAllowed && user.gmsPublishName === PublishNameType.PUBLISH_NAME_FIRST_INITIAL) {
|
||||
return this.firstUpperCaseSecondLowerCase(user.lastName)
|
||||
}
|
||||
return null // cause to delete lastname in gms
|
||||
|
||||
/*
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
((!user.alias && user.gmsPublishName === PublishNameType.PUBLISH_NAME_ALIAS_OR_INITALS) ||
|
||||
user.gmsPublishName === PublishNameType.PUBLISH_NAME_FIRST_INITIAL ||
|
||||
user.gmsPublishName === PublishNameType.PUBLISH_NAME_INITIALS)
|
||||
) {
|
||||
return user.lastName.substring(0, 1)
|
||||
return this.firstUpperCaseSecondLowerCase(user.lastName)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private getGmsEmail(user: dbUser): string | undefined {
|
||||
@ -106,4 +124,11 @@ export class GmsUser {
|
||||
return user.emailContact.phone
|
||||
}
|
||||
}
|
||||
|
||||
private firstUpperCaseSecondLowerCase(name: string) {
|
||||
if (name && name.length >= 2) {
|
||||
return name.charAt(0).toUpperCase() + name.charAt(1).toLocaleLowerCase()
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
12
backend/src/graphql/model/UserLocationResult.ts
Normal file
12
backend/src/graphql/model/UserLocationResult.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Field, ObjectType } from 'type-graphql'
|
||||
|
||||
import { Location } from './Location'
|
||||
|
||||
@ObjectType()
|
||||
export class UserLocationResult {
|
||||
@Field(() => Location)
|
||||
userLocation: Location
|
||||
|
||||
@Field(() => Location)
|
||||
communityLocation: Location
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { getConnection, In } from '@dbTools/typeorm'
|
||||
import { getConnection, In, Point } from '@dbTools/typeorm'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
@ -28,9 +28,8 @@ import { SearchAdminUsersResult } from '@model/AdminUser'
|
||||
import { GmsUserAuthenticationResult } from '@model/GmsUserAuthenticationResult'
|
||||
import { User } from '@model/User'
|
||||
import { UserAdmin, SearchUsersResult } from '@model/UserAdmin'
|
||||
import { UserLocationResult } from '@model/UserLocationResult'
|
||||
|
||||
import { updateGmsUser } from '@/apis/gms/GmsClient'
|
||||
import { GmsUser } from '@/apis/gms/model/GmsUser'
|
||||
import { HumHubClient } from '@/apis/humhub/HumHubClient'
|
||||
import { GetUser } from '@/apis/humhub/model/GetUser'
|
||||
import { PostUser } from '@/apis/humhub/model/PostUser'
|
||||
@ -81,7 +80,7 @@ import { getUserCreations } from './util/creations'
|
||||
import { findUserByIdentifier } from './util/findUserByIdentifier'
|
||||
import { findUsers } from './util/findUsers'
|
||||
import { getKlicktippState } from './util/getKlicktippState'
|
||||
import { Location2Point } from './util/Location2Point'
|
||||
import { Location2Point, Point2Location } from './util/Location2Point'
|
||||
import { setUserRole, deleteUserRole } from './util/modifyUserRole'
|
||||
import { sendUserToGms } from './util/sendUserToGms'
|
||||
import { syncHumhub } from './util/syncHumhub'
|
||||
@ -696,9 +695,9 @@ export class UserResolver {
|
||||
logger.debug(`changed user-settings relevant for gms-user update...`)
|
||||
const homeCom = await getHomeCommunity()
|
||||
if (homeCom.gmsApiKey !== null) {
|
||||
logger.debug(`gms-user update...`, user)
|
||||
await updateGmsUser(homeCom.gmsApiKey, new GmsUser(user))
|
||||
logger.debug(`gms-user update successfully.`)
|
||||
logger.debug(`send User to Gms...`, user)
|
||||
await sendUserToGms(user, homeCom)
|
||||
logger.debug(`sendUserToGms successfully.`)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@ -728,14 +727,35 @@ export class UserResolver {
|
||||
@Authorized([RIGHTS.GMS_USER_PLAYGROUND])
|
||||
@Query(() => GmsUserAuthenticationResult)
|
||||
async authenticateGmsUserSearch(@Ctx() context: Context): Promise<GmsUserAuthenticationResult> {
|
||||
logger.info(`authUserForGmsUserSearch()...`)
|
||||
logger.info(`authenticateGmsUserSearch()...`)
|
||||
const dbUser = getUser(context)
|
||||
let result: GmsUserAuthenticationResult
|
||||
let result = new GmsUserAuthenticationResult()
|
||||
if (context.token) {
|
||||
result = await authenticateGmsUserPlayground(context.token, dbUser)
|
||||
logger.info('authUserForGmsUserSearch=', result)
|
||||
const homeCom = await getHomeCommunity()
|
||||
if (!homeCom.gmsApiKey) {
|
||||
throw new LogError('authenticateGmsUserSearch missing HomeCommunity GmsApiKey')
|
||||
}
|
||||
result = await authenticateGmsUserPlayground(homeCom.gmsApiKey, context.token, dbUser)
|
||||
logger.info('authenticateGmsUserSearch=', result)
|
||||
} else {
|
||||
throw new LogError('authUserForGmsUserSearch without token')
|
||||
throw new LogError('authenticateGmsUserSearch missing valid user login-token')
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.GMS_USER_PLAYGROUND])
|
||||
@Query(() => UserLocationResult)
|
||||
async userLocation(@Ctx() context: Context): Promise<UserLocationResult> {
|
||||
logger.info(`userLocation()...`)
|
||||
const dbUser = getUser(context)
|
||||
const result = new UserLocationResult()
|
||||
if (context.token) {
|
||||
const homeCom = await getHomeCommunity()
|
||||
result.communityLocation = Point2Location(homeCom.location as Point)
|
||||
result.userLocation = Point2Location(dbUser.location as Point)
|
||||
logger.info('userLocation=', result)
|
||||
} else {
|
||||
throw new LogError('userLocation missing valid user login-token')
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@ -7,13 +7,14 @@ import { backendLogger as logger } from '@/server/logger'
|
||||
import { ensureUrlEndsWithSlash } from '@/util/utilities'
|
||||
|
||||
export async function authenticateGmsUserPlayground(
|
||||
apiKey: string,
|
||||
token: string,
|
||||
dbUser: DbUser,
|
||||
): Promise<GmsUserAuthenticationResult> {
|
||||
const result = new GmsUserAuthenticationResult()
|
||||
const dashboardUrl = ensureUrlEndsWithSlash(CONFIG.GMS_DASHBOARD_URL)
|
||||
|
||||
result.url = dashboardUrl.concat('playground')
|
||||
result.url = dashboardUrl.concat('usersearch-playground')
|
||||
result.token = await verifyAuthToken(dbUser.communityUuid, token)
|
||||
logger.info('GmsUserAuthenticationResult:', result)
|
||||
return result
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { createGmsUser } from '@/apis/gms/GmsClient'
|
||||
import { createGmsUser, updateGmsUser } from '@/apis/gms/GmsClient'
|
||||
import { GmsUser } from '@/apis/gms/model/GmsUser'
|
||||
import { CONFIG } from '@/config'
|
||||
import { LogError } from '@/server/LogError'
|
||||
@ -14,13 +14,20 @@ export async function sendUserToGms(user: DbUser, homeCom: DbCommunity): Promise
|
||||
logger.debug('User send to GMS:', user)
|
||||
const gmsUser = new GmsUser(user)
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
if (await createGmsUser(homeCom.gmsApiKey, gmsUser)) {
|
||||
logger.debug('GMS user published successfully:', gmsUser)
|
||||
user.gmsRegistered = true
|
||||
user.gmsRegisteredAt = new Date()
|
||||
await DbUser.save(user)
|
||||
logger.debug('mark user as gms published:', user)
|
||||
if (!user.gmsRegistered && user.gmsRegisteredAt === null) {
|
||||
logger.debug('create user in gms:', gmsUser)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
if (await createGmsUser(homeCom.gmsApiKey, gmsUser)) {
|
||||
logger.debug('GMS user published successfully:', gmsUser)
|
||||
await updateUserGmsStatus(user)
|
||||
}
|
||||
} else {
|
||||
logger.debug('update user in gms:', gmsUser)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
if (await updateGmsUser(homeCom.gmsApiKey, gmsUser)) {
|
||||
logger.debug('GMS user published successfully:', gmsUser)
|
||||
await updateUserGmsStatus(user)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (CONFIG.GMS_CREATE_USER_THROW_ERRORS) {
|
||||
@ -30,3 +37,11 @@ export async function sendUserToGms(user: DbUser, homeCom: DbCommunity): Promise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateUserGmsStatus(user: DbUser) {
|
||||
logger.debug('updateUserGmsStatus:', user)
|
||||
user.gmsRegistered = true
|
||||
user.gmsRegisteredAt = new Date()
|
||||
await DbUser.save(user)
|
||||
logger.debug('mark user as gms published:', user)
|
||||
}
|
||||
|
||||
@ -59,8 +59,8 @@ const run = async () => {
|
||||
const userIds = await DbUser.createQueryBuilder()
|
||||
.select('id')
|
||||
.where({ foreign: false })
|
||||
.andWhere('deleted_at is null')
|
||||
.andWhere({ gmsRegistered: false })
|
||||
// .andWhere('deleted_at is null')
|
||||
// .andWhere({ gmsRegistered: false })
|
||||
.getRawMany()
|
||||
logger.debug('userIds:', userIds)
|
||||
|
||||
|
||||
@ -24,6 +24,15 @@ export const authenticateGmsUserSearch = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const userLocationQuery = gql`
|
||||
query {
|
||||
userLocation {
|
||||
userLocation
|
||||
communityLocation
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const queryOptIn = gql`
|
||||
query ($optIn: String!) {
|
||||
queryOptIn(optIn: $optIn)
|
||||
|
||||
@ -35,8 +35,8 @@ export default defineConfig({
|
||||
excludeSpecPattern: '*.js',
|
||||
baseUrl: 'http://localhost:3000',
|
||||
chromeWebSecurity: false,
|
||||
defaultCommandTimeout: 100000,
|
||||
pageLoadTimeout: 120000,
|
||||
defaultCommandTimeout: 200000,
|
||||
pageLoadTimeout: 240000,
|
||||
supportFile: 'cypress/support/index.ts',
|
||||
viewportHeight: 720,
|
||||
viewportWidth: 1280,
|
||||
|
||||
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
@ -2,6 +2,8 @@
|
||||
node_modules/
|
||||
build/
|
||||
.cache/
|
||||
.yarn/install-state.gz
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
@ -76,7 +76,7 @@ FROM build as test
|
||||
RUN apk add --no-cache bash jq
|
||||
|
||||
# Run command
|
||||
CMD /bin/sh -c "yarn run dev"
|
||||
CMD /bin/sh -c "yarn run start"
|
||||
|
||||
##################################################################################
|
||||
# PRODUCTION (Does contain only "binary"- and static-files to reduce image size) #
|
||||
|
||||
@ -23,9 +23,9 @@
|
||||
"@babel/preset-env": "^7.13.12",
|
||||
"@morev/vue-transitions": "^3.0.2",
|
||||
"@types/leaflet": "^1.9.12",
|
||||
"@vee-validate/i18n": "^4.13.2",
|
||||
"@vee-validate/rules": "^4.13.2",
|
||||
"@vee-validate/yup": "^4.13.2",
|
||||
"@vee-validate/i18n": "^4.14.7",
|
||||
"@vee-validate/rules": "^4.14.1",
|
||||
"@vee-validate/yup": "^4.14.1",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
"@vue/apollo-composable": "^4.0.2",
|
||||
@ -35,7 +35,7 @@
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-preset-vue": "^2.0.2",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-vue-next": "^0.23.3",
|
||||
"bootstrap-vue-next": "0.26.8",
|
||||
"clipboard-polyfill": "^4.0.0-rc1",
|
||||
"date-fns": "^2.29.3",
|
||||
"es6-promise": "^4.1.1",
|
||||
@ -56,7 +56,7 @@
|
||||
"vee-validate": "^4.13.2",
|
||||
"vite": "3.2.10",
|
||||
"vite-plugin-commonjs": "^0.10.1",
|
||||
"vue": "3.4.31",
|
||||
"vue": "3.5.13",
|
||||
"vue-apollo": "^3.1.2",
|
||||
"vue-flatpickr-component": "^8.1.2",
|
||||
"vue-i18n": "^9.13.1",
|
||||
@ -93,7 +93,7 @@
|
||||
"postcss-html": "^1.3.0",
|
||||
"postcss-scss": "^4.0.3",
|
||||
"prettier": "^3.3.3",
|
||||
"sass": "1.32.13",
|
||||
"sass": "1.77.6",
|
||||
"stylelint": "16.7.0",
|
||||
"stylelint-config-recommended-vue": "1.5.0",
|
||||
"stylelint-config-standard-scss": "13.1.0",
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
// Imports
|
||||
const dotenv = require('dotenv')
|
||||
const express = require('express')
|
||||
const path = require('path')
|
||||
|
||||
dotenv.config() // load env vars from .env
|
||||
|
||||
const CONFIG = require('../src/config')
|
||||
|
||||
// Host & Port
|
||||
const hostname = '127.0.0.1'
|
||||
const port = import.meta.env.PORT || 3000
|
||||
const hostname = CONFIG.FRONTEND_MODULE_HOST
|
||||
const port = CONFIG.FRONTEND_MODULE_PORT
|
||||
|
||||
// Express Server
|
||||
const app = express()
|
||||
|
||||
@ -19,7 +19,7 @@ $h3-font-size: $font-size-base * 1.0625 ;
|
||||
$h4-font-size: $font-size-base * 0.9375 ;
|
||||
$h5-font-size: $font-size-base * 0.8125 ;
|
||||
$h6-font-size: $font-size-base * 0.625 ;
|
||||
$headings-margin-bottom: ($spacer / 2);
|
||||
$headings-margin-bottom: calc($spacer / 2);
|
||||
$headings-font-family: inherit ;
|
||||
$headings-font-weight: $font-weight-bold ;
|
||||
$headings-line-height: 1.5 ;
|
||||
|
||||
@ -1,13 +1,24 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import CollapseLinksList from './CollapseLinksList'
|
||||
import CollapseLinksList from './CollapseLinksList.vue'
|
||||
import { createStore } from 'vuex'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import { BButton } from 'bootstrap-vue-next'
|
||||
|
||||
// Mock vue-i18n
|
||||
const mockT = vi.fn((key, value) => `${key} ${value}`)
|
||||
// Mock translations
|
||||
const mockT = vi.fn((key, params) => {
|
||||
switch (key) {
|
||||
case 'link-load':
|
||||
if (params === 0) return '1 more link'
|
||||
return `${params.n} more links`
|
||||
case 'link-load-more':
|
||||
return `Load ${params.n} more links`
|
||||
default:
|
||||
return key
|
||||
}
|
||||
})
|
||||
|
||||
// Mock vue-i18n
|
||||
vi.mock('vue-i18n', () => ({
|
||||
createI18n: vi.fn(() => ({
|
||||
global: {
|
||||
@ -64,6 +75,7 @@ describe('CollapseLinksList', () => {
|
||||
},
|
||||
},
|
||||
props: {
|
||||
modelValue: 1,
|
||||
transactionLinks: [
|
||||
{
|
||||
amount: '5',
|
||||
@ -89,7 +101,6 @@ describe('CollapseLinksList', () => {
|
||||
},
|
||||
],
|
||||
transactionLinkCount: 3,
|
||||
value: 1,
|
||||
pending: false,
|
||||
pageSize: 5,
|
||||
...props,
|
||||
@ -111,8 +122,8 @@ describe('CollapseLinksList', () => {
|
||||
await wrapper.find('.test-button-load-more').trigger('click')
|
||||
})
|
||||
|
||||
it('emits input', () => {
|
||||
expect(wrapper.emitted('input')).toEqual([[2]])
|
||||
it('emits update:modelValue', () => {
|
||||
expect(wrapper.emitted('update:modelValue')).toEqual([[2]])
|
||||
})
|
||||
})
|
||||
|
||||
@ -123,8 +134,8 @@ describe('CollapseLinksList', () => {
|
||||
.vm.$emit('reset-transaction-link-list')
|
||||
})
|
||||
|
||||
it('emits input ', () => {
|
||||
expect(wrapper.emitted('input')).toEqual([[0]])
|
||||
it('emits update:modelValue', () => {
|
||||
expect(wrapper.emitted('update:modelValue')).toEqual([[0]])
|
||||
})
|
||||
})
|
||||
|
||||
@ -138,7 +149,7 @@ describe('CollapseLinksList', () => {
|
||||
})
|
||||
|
||||
it('renders text in singular', () => {
|
||||
expect(mockT).toHaveBeenCalledWith('link-load', 0)
|
||||
expect(wrapper.find('.test-button-load-more').text()).toBe('1 more link')
|
||||
})
|
||||
})
|
||||
|
||||
@ -151,7 +162,7 @@ describe('CollapseLinksList', () => {
|
||||
})
|
||||
|
||||
it('renders text in plural and shows the correct count of links', () => {
|
||||
expect(mockT).toHaveBeenCalledWith('link-load', 0)
|
||||
expect(wrapper.find('.test-button-load-more').text()).toBe('4 more links')
|
||||
})
|
||||
})
|
||||
|
||||
@ -162,11 +173,10 @@ describe('CollapseLinksList', () => {
|
||||
transactionLinks: [{ id: 1 }, { id: 2 }],
|
||||
pageSize: 5,
|
||||
})
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('renders text with pageSize as number of links to load', () => {
|
||||
expect(mockT).toHaveBeenCalledWith('link-load-more', { n: 5 })
|
||||
expect(wrapper.find('.test-button-load-more').text()).toBe('Load 5 more links')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -42,7 +42,7 @@ export default {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
value: { type: Number, required: true },
|
||||
modelValue: { type: Number, required: true },
|
||||
pageSize: { type: Number, default: 5 },
|
||||
pending: { type: Boolean, default: false },
|
||||
},
|
||||
@ -56,10 +56,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
resetTransactionLinkList() {
|
||||
this.$emit('input', 0)
|
||||
this.$emit('update:modelValue', 0)
|
||||
},
|
||||
loadMoreLinks() {
|
||||
this.$emit('input', this.value + 1)
|
||||
this.$emit('update:modelValue', this.modelValue + 1)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
block
|
||||
variant="gradido"
|
||||
:disabled="disabled"
|
||||
@click="$emit('send-transaction'), (disabled = true)"
|
||||
@click="($emit('send-transaction'), (disabled = true))"
|
||||
>
|
||||
{{ $t('form.send_now') }}
|
||||
</BButton>
|
||||
|
||||
@ -69,12 +69,6 @@ describe('InputUsername', () => {
|
||||
expect(input.props('placeholder')).toBe('Username')
|
||||
})
|
||||
|
||||
it('emits set-is-edit event when button is clicked', async () => {
|
||||
const button = wrapper.findComponent({ name: 'BButton' })
|
||||
await button.trigger('click')
|
||||
expect(wrapper.emitted('set-is-edit')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows all errors when showAllErrors prop is true', async () => {
|
||||
const errors = ['Error 1', 'Error 2']
|
||||
vi.mocked(useField).mockReturnValue({
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
data-test="username"
|
||||
@update:modelValue="usernameValue = $event"
|
||||
/>
|
||||
<BButton size="md" text="Button" variant="secondary" append @click="emitSetIsEdit">
|
||||
<BButton size="md" text="Button" variant="secondary" append @click="clearInput">
|
||||
<IBiXCircle style="height: 17px; width: 17px" />
|
||||
</BButton>
|
||||
</BInputGroup>
|
||||
@ -47,18 +47,17 @@ import { ref, computed, watch, defineProps, defineEmits } from 'vue'
|
||||
import { useField, useForm } from 'vee-validate'
|
||||
|
||||
const props = defineProps({
|
||||
isEdit: { type: Boolean, default: false },
|
||||
rules: { type: Object, default: () => ({ required: true }) },
|
||||
name: { type: String, default: 'username' },
|
||||
label: { type: String, default: 'Username' },
|
||||
placeholder: { type: String, default: 'Username' },
|
||||
modelValue: { type: String, required: true },
|
||||
showAllErrors: { type: Boolean, default: false },
|
||||
immediate: { type: Boolean, default: false },
|
||||
unique: { type: Boolean, required: true },
|
||||
initialUsernameValue: { type: String, default: '' },
|
||||
})
|
||||
|
||||
const currentValue = ref(props?.modelValue)
|
||||
const currentValue = ref(props.initialUsernameValue)
|
||||
|
||||
const {
|
||||
meta: usernameMeta,
|
||||
@ -69,11 +68,13 @@ const {
|
||||
initialValue: currentValue,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'set-is-edit'])
|
||||
const clearInput = () => (usernameValue.value = '')
|
||||
|
||||
const labelFor = computed(() => `${props.name}-input-field`)
|
||||
|
||||
const emitSetIsEdit = (bool) => {
|
||||
emit('set-is-edit', bool)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div#username-form > div > label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,242 +1,3 @@
|
||||
// import { mount } from '@vue/test-utils'
|
||||
// import Transaction from './Transaction'
|
||||
// import Vue from 'vue'
|
||||
// import flushPromises from 'flush-promises'
|
||||
//
|
||||
// const localVue = global.localVue
|
||||
//
|
||||
// const consoleErrorMock = jest.fn()
|
||||
//
|
||||
// describe('Transaction', () => {
|
||||
// let wrapper
|
||||
//
|
||||
// const mocks = {
|
||||
// $i18n: {
|
||||
// locale: 'en',
|
||||
// },
|
||||
// $t: jest.fn((t) => t),
|
||||
// $n: jest.fn((n) => n),
|
||||
// $d: jest.fn((d) => d),
|
||||
// }
|
||||
//
|
||||
// const Wrapper = () => {
|
||||
// return mount(Transaction, { localVue, mocks })
|
||||
// }
|
||||
//
|
||||
// describe('mount', () => {
|
||||
// beforeEach(() => {
|
||||
// wrapper = Wrapper()
|
||||
// })
|
||||
//
|
||||
// it('renders the component', () => {
|
||||
// expect(wrapper.find('div.gdt-transaction-list').exists()).toBeTruthy()
|
||||
// })
|
||||
//
|
||||
// it('has a collapse icon bi-arrow-down-circle', () => {
|
||||
// expect(wrapper.find('div.gdt-transaction-list').findAll('svg').at(1).classes()).toEqual([
|
||||
// 'bi-arrow-down-circle',
|
||||
// 'h1',
|
||||
// 'b-icon',
|
||||
// 'bi',
|
||||
// 'text-muted',
|
||||
// ])
|
||||
// })
|
||||
//
|
||||
// describe('no valid GDT entry type', () => {
|
||||
// beforeEach(async () => {
|
||||
// // disable throwing Errors on warnings to catch the warning
|
||||
// Vue.config.warnHandler = (w) => {}
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.error = consoleErrorMock
|
||||
// await wrapper.setProps({ gdtEntryType: 'NOT_VALID' })
|
||||
// })
|
||||
//
|
||||
// it('throws an error', () => {
|
||||
// expect(consoleErrorMock).toBeCalledWith(
|
||||
// expect.objectContaining({ message: 'no lines for this type: NOT_VALID' }),
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('default entry type FORM', () => {
|
||||
// beforeEach(async () => {
|
||||
// await wrapper.setProps({
|
||||
// amount: 100,
|
||||
// date: '2021-05-02T17:20:11+00:00',
|
||||
// comment: 'This is a comment',
|
||||
// factor: 17,
|
||||
// gdt: 1700,
|
||||
// id: 42,
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// it('has the heart icon', () => {
|
||||
// expect(wrapper.find('svg.bi-heart').exists()).toBeTruthy()
|
||||
// })
|
||||
//
|
||||
// it('has the description gdt.contribution', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('gdt.contribution')
|
||||
// })
|
||||
//
|
||||
// it('renders the amount of euros', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('100 €')
|
||||
// })
|
||||
//
|
||||
// it('renders the amount of GDT', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('1700 GDT')
|
||||
// })
|
||||
//
|
||||
// it.skip('renders the comment message', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('This is a comment')
|
||||
// })
|
||||
//
|
||||
// it.skip('renders the date', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('Sun May 02 2021')
|
||||
// })
|
||||
//
|
||||
// it('does not show the collapse by default', () => {
|
||||
// expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBeFalsy()
|
||||
// })
|
||||
//
|
||||
// describe('without comment', () => {
|
||||
// it('does not render the message row', async () => {
|
||||
// await wrapper.setProps({ comment: undefined })
|
||||
// expect(wrapper.findAll('div.row').at(1).text()).toContain('gdt.calculation')
|
||||
// })
|
||||
// })
|
||||
// // how to open the collapse ?????
|
||||
// describe.skip('collapse is open', () => {
|
||||
// beforeEach(async () => {
|
||||
// await wrapper.find('div#gdt-collapse-42').trigger('click')
|
||||
// await wrapper.vm.$nextTick()
|
||||
// await flushPromises()
|
||||
// await wrapper.vm.$nextTick()
|
||||
// await flushPromises()
|
||||
// })
|
||||
//
|
||||
// it('shows the collapse', () => {
|
||||
// expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBeTruthy()
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('GdtEntryType.CVS', () => {
|
||||
// it('behaves as default FORM', async () => {
|
||||
// await wrapper.setProps({ gdtEntryType: 'CVS' })
|
||||
// expect(wrapper.find('svg.bi-heart').exists()).toBeTruthy()
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('GdtEntryType.ELOPAGE', () => {
|
||||
// it('behaves as default FORM', async () => {
|
||||
// await wrapper.setProps({ gdtEntryType: 'ELOPAGE' })
|
||||
// expect(wrapper.find('svg.bi-heart').exists()).toBeTruthy()
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('GdtEntryType.DIGISTORE', () => {
|
||||
// it('behaves as default FORM', async () => {
|
||||
// await wrapper.setProps({ gdtEntryType: 'DIGISTORE' })
|
||||
// expect(wrapper.find('svg.bi-heart').exists()).toBeTruthy()
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('GdtEntryType.CVS2', () => {
|
||||
// it('behaves as default FORM', async () => {
|
||||
// await wrapper.setProps({ gdtEntryType: 'CVS2' })
|
||||
// expect(wrapper.find('svg.bi-heart').exists()).toBeTruthy()
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('GdtEntryType.ELOPAGE_PUBLISHER', () => {
|
||||
// beforeEach(async () => {
|
||||
// await wrapper.setProps({
|
||||
// amount: 365.67,
|
||||
// date: '2020-04-10T13:28:00+00:00',
|
||||
// comment: 'This is a comment',
|
||||
// gdtEntryType: 'ELOPAGE_PUBLISHER',
|
||||
// factor: 22,
|
||||
// gdt: 967.65,
|
||||
// id: 42,
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// it('has the person-check icon', () => {
|
||||
// expect(wrapper.find('svg.bi-person-check').exists()).toBeTruthy()
|
||||
// })
|
||||
//
|
||||
// it('has the description gdt.recruited-member', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('gdt.recruited-member')
|
||||
// })
|
||||
//
|
||||
// it('renders the percentage', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('5%')
|
||||
// })
|
||||
//
|
||||
// it('renders the amount of GDT', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('365.67 GDT')
|
||||
// })
|
||||
//
|
||||
// it('renders the gdt.publisher', () => {
|
||||
// expect(wrapper.findAll('div.row').at(1).text()).toContain('gdt.publisher')
|
||||
// })
|
||||
//
|
||||
// it.skip('renders the date', () => {
|
||||
// expect(wrapper.findAll('div.row').at(2).text()).toContain('Fri Apr 10 2020')
|
||||
// })
|
||||
//
|
||||
// it('does not show the collapse by default', () => {
|
||||
// expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBeFalsy()
|
||||
// })
|
||||
//
|
||||
// describe.skip('without comment', () => {
|
||||
// it('does not render the message row', async () => {
|
||||
// await wrapper.setProps({ comment: undefined })
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('form.date')
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('GdtEntryType.GLOBAL_MODIFICATOR', () => {
|
||||
// beforeEach(async () => {
|
||||
// await wrapper.setProps({
|
||||
// amount: 123.45,
|
||||
// date: '2020-03-12T13:28:00+00:00',
|
||||
// comment: 'This is a comment',
|
||||
// gdtEntryType: 'GLOBAL_MODIFICATOR',
|
||||
// factor: 19,
|
||||
// gdt: 61.23,
|
||||
// id: 42,
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// it('has the gift icon', () => {
|
||||
// expect(wrapper.find('svg.bi-gift').exists()).toBeTruthy()
|
||||
// })
|
||||
//
|
||||
// it('has the description gdt.gdt-received', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('gdt.gdt-received')
|
||||
// })
|
||||
//
|
||||
// it('renders the comment', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('This is a comment')
|
||||
// })
|
||||
//
|
||||
// it('renders the amount of GDT', () => {
|
||||
// expect(wrapper.findAll('div.row').at(0).text()).toContain('61.23 GDT')
|
||||
// })
|
||||
//
|
||||
// it('renders the gdt.conversion-gdt-euro', () => {
|
||||
// expect(wrapper.findAll('div.row').at(1).text()).toContain('gdt.conversion-gdt-euro')
|
||||
// })
|
||||
//
|
||||
// it('does not show the collapse by default', () => {
|
||||
// expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBeFalsy()
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import Transaction from './Transaction'
|
||||
@ -266,8 +27,11 @@ vi.mock('vue-i18n', () => ({
|
||||
describe('Transaction', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
const createWrapper = (props = {}, options = {}) => {
|
||||
return mount(Transaction, {
|
||||
props: {
|
||||
...props,
|
||||
},
|
||||
global: {
|
||||
plugins: [
|
||||
createStore({
|
||||
@ -285,8 +49,21 @@ describe('Transaction', () => {
|
||||
BRow,
|
||||
BCol,
|
||||
BAvatar,
|
||||
BCollapse,
|
||||
BCollapse: {
|
||||
template: `
|
||||
<div
|
||||
:id="id"
|
||||
class="collapse"
|
||||
:class="{ show: modelValue }"
|
||||
data-test="collapse"
|
||||
>
|
||||
<slot/>
|
||||
</div>
|
||||
`,
|
||||
props: ['id', 'modelValue'],
|
||||
},
|
||||
VariantIcon,
|
||||
...options.stubs,
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -294,7 +71,7 @@ describe('Transaction', () => {
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper = createWrapper()
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
@ -330,7 +107,7 @@ describe('Transaction', () => {
|
||||
|
||||
describe('default entry type FORM', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setProps({
|
||||
wrapper = createWrapper({
|
||||
amount: 100,
|
||||
date: '2021-05-02T17:20:11+00:00',
|
||||
comment: 'This is a comment',
|
||||
@ -356,10 +133,6 @@ describe('Transaction', () => {
|
||||
expect(wrapper.findAll('div.row').at(0).text()).toContain('1700 GDT')
|
||||
})
|
||||
|
||||
// it('does not show the collapse by default', () => {
|
||||
// expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBe(false)
|
||||
// })
|
||||
|
||||
describe('without comment', () => {
|
||||
it('does not render the message row', async () => {
|
||||
await wrapper.setProps({ comment: undefined })
|
||||
@ -374,7 +147,10 @@ describe('Transaction', () => {
|
||||
})
|
||||
|
||||
it('shows the collapse', () => {
|
||||
expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBe(true)
|
||||
const collapse = wrapper.find('div#gdt-collapse-42')
|
||||
expect(collapse.exists()).toBe(true)
|
||||
expect(collapse.attributes('data-test')).toBe('collapse')
|
||||
expect(wrapper.find('[data-test="collapse"]').classes()).toContain('show')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -409,7 +185,7 @@ describe('Transaction', () => {
|
||||
|
||||
describe('GdtEntryType.ELOPAGE_PUBLISHER', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setProps({
|
||||
wrapper = createWrapper({
|
||||
amount: 365.67,
|
||||
date: '2020-04-10T13:28:00+00:00',
|
||||
comment: 'This is a comment',
|
||||
@ -435,15 +211,11 @@ describe('Transaction', () => {
|
||||
it('renders the amount of GDT', () => {
|
||||
expect(wrapper.findAll('div.row').at(0).text()).toContain('365.67 GDT')
|
||||
})
|
||||
|
||||
// it('does not show the collapse by default', () => {
|
||||
// expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBe(false)
|
||||
// })
|
||||
})
|
||||
|
||||
describe('GdtEntryType.GLOBAL_MODIFICATOR', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setProps({
|
||||
wrapper = createWrapper({
|
||||
amount: 123.45,
|
||||
date: '2020-03-12T13:28:00+00:00',
|
||||
comment: 'This is a comment',
|
||||
@ -452,7 +224,6 @@ describe('Transaction', () => {
|
||||
gdt: 61.23,
|
||||
id: 42,
|
||||
})
|
||||
wrapper.attachTo = document.body
|
||||
})
|
||||
|
||||
it('has the gift icon', () => {
|
||||
@ -470,10 +241,6 @@ describe('Transaction', () => {
|
||||
it('renders the amount of GDT', () => {
|
||||
expect(wrapper.findAll('div.row').at(0).text()).toContain('61.23 GDT')
|
||||
})
|
||||
|
||||
// it('does not show the collapse by default', () => {
|
||||
// expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBe(false)
|
||||
// })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,7 +14,10 @@
|
||||
</BCol>
|
||||
<BCol>
|
||||
<div>
|
||||
<component :is="nameComponent" v-bind="nameProps" />
|
||||
<Name v-if="useNameComponent" v-bind="nameProps" />
|
||||
<div v-else :class="nameProps.class">
|
||||
{{ nameProps.creationLinkedUser }}
|
||||
</div>
|
||||
</div>
|
||||
<span class="small">{{ $d(new Date(props.transaction.balanceDate), 'short') }}</span>
|
||||
<span class="ms-4 small">{{ $d(new Date(props.transaction.balanceDate), 'time') }}</span>
|
||||
@ -112,14 +115,15 @@ const avatarProps = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const nameComponent = computed(() => {
|
||||
return isCreationType.value ? 'div' : Name
|
||||
const useNameComponent = computed(() => {
|
||||
return !isCreationType.value
|
||||
})
|
||||
|
||||
const nameProps = computed(() => {
|
||||
if (isCreationType.value) {
|
||||
return {
|
||||
class: 'fw-bold',
|
||||
creationLinkedUser: `${props.transaction.linkedUser.firstName} ${props.transaction.linkedUser.lastName}`,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div class="user-gms-location-format">
|
||||
<BDropdown v-model="selectedOption">
|
||||
<template #button-content>{{ selectedOptionLabel }}</template>
|
||||
<BDropdown :text="selectedOptionLabel">
|
||||
<BDropdownItem
|
||||
v-for="option in dropdownOptions"
|
||||
:key="option.value"
|
||||
|
||||
@ -47,11 +47,12 @@ const communityLocation = ref({ lat: 0, lng: 0 })
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
onResult(({ data }) => {
|
||||
communityLocation.value.lng = data.userLocation.longitude
|
||||
communityLocation.value.lat = data.userLocation.latitude
|
||||
const locationData = data.userLocation
|
||||
communityLocation.value.lng = locationData.communityLocation.longitude
|
||||
communityLocation.value.lat = locationData.communityLocation.latitude
|
||||
|
||||
userLocation.value.lng = data?.userLocation?.longitude ?? communityLocation.value.lng
|
||||
userLocation.value.lat = data?.userLocation?.latitude ?? communityLocation.value.lat
|
||||
userLocation.value.lng = locationData.userLocation?.longitude ?? communityLocation.value.lng
|
||||
userLocation.value.lat = locationData.userLocation?.latitude ?? communityLocation.value.lat
|
||||
})
|
||||
|
||||
onError((err) => {
|
||||
@ -79,6 +80,7 @@ const saveUserLocation = async () => {
|
||||
},
|
||||
})
|
||||
toastSuccess(t('settings.GMS.location.updateSuccess'))
|
||||
userLocation.value = capturedLocation.value
|
||||
isModalOpen.value = false
|
||||
} catch (error) {
|
||||
toastError(error.message)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<coordinates-display
|
||||
v-if="map"
|
||||
:community-position="communityPosition"
|
||||
:user-position="userPosition"
|
||||
@centerMap="handleMapCenter"
|
||||
@ -21,7 +22,6 @@ const mapContainer = ref(null)
|
||||
const map = ref(null)
|
||||
const userMarker = ref(null)
|
||||
const communityMarker = ref(null)
|
||||
const searchQuery = ref('')
|
||||
const userPosition = ref({ lat: 0, lng: 0 })
|
||||
const communityPosition = ref({ lat: 0, lng: 0 })
|
||||
const defaultZoom = 13
|
||||
@ -42,8 +42,7 @@ onMounted(async () => {
|
||||
if (props.communityMarkerCoords) {
|
||||
communityPosition.value = props.communityMarkerCoords
|
||||
}
|
||||
await nextTick()
|
||||
initMap()
|
||||
setTimeout(() => initMap(), 250)
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
@ -60,6 +59,7 @@ function initMap() {
|
||||
center: [userPosition.value.lat, userPosition.value.lng],
|
||||
zoom: defaultZoom,
|
||||
zoomControl: false,
|
||||
closePopupOnClick: false,
|
||||
})
|
||||
|
||||
L.control.zoom({ position: 'topleft' }).addTo(map.value)
|
||||
@ -72,6 +72,7 @@ function initMap() {
|
||||
// User marker (movable)
|
||||
userMarker.value = L.marker([userPosition.value.lat, userPosition.value.lng], {
|
||||
draggable: true,
|
||||
interactive: false,
|
||||
icon: L.icon({
|
||||
iconUrl:
|
||||
'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-blue.png',
|
||||
@ -83,11 +84,18 @@ function initMap() {
|
||||
}),
|
||||
}).addTo(map.value)
|
||||
|
||||
userMarker.value.bindPopup(t('settings.GMS.map.userLocationLabel')).openPopup()
|
||||
userMarker.value
|
||||
.bindPopup(t('settings.GMS.map.userLocationLabel'), {
|
||||
autoClose: false,
|
||||
closeOnClick: false,
|
||||
closeButton: false,
|
||||
})
|
||||
.openPopup()
|
||||
|
||||
// Community marker (fixed)
|
||||
communityMarker.value = L.marker([communityPosition.value.lat, communityPosition.value.lng], {
|
||||
draggable: false,
|
||||
interactive: false,
|
||||
icon: L.icon({
|
||||
iconUrl:
|
||||
'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
||||
@ -99,7 +107,13 @@ function initMap() {
|
||||
}),
|
||||
}).addTo(map.value)
|
||||
|
||||
communityMarker.value.bindPopup(t('settings.GMS.map.communityLocationLabel'))
|
||||
communityMarker.value
|
||||
.bindPopup(t('settings.GMS.map.communityLocationLabel'), {
|
||||
autoClose: false,
|
||||
closeOnClick: false,
|
||||
closeButton: false,
|
||||
})
|
||||
.openPopup()
|
||||
|
||||
map.value.on('click', onMapClick)
|
||||
userMarker.value.on('dragend', onMarkerDragEnd)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
import UserName from './UserName.vue'
|
||||
import { createStore } from 'vuex'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
@ -60,20 +61,22 @@ vi.mock('@/composables/useToast', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
const valuesMock = {}
|
||||
const errorsMock = {}
|
||||
// Updated to use Vue's reactivity
|
||||
const valuesMock = ref({ username: '' })
|
||||
const errorsMock = ref({})
|
||||
const setFieldValueMock = vi.fn((field, value) => {
|
||||
valuesMock[field] = value
|
||||
valuesMock.value[field] = value
|
||||
})
|
||||
const handleSubmitMock = vi.fn((callback) => {
|
||||
return () => callback(valuesMock)
|
||||
return () => callback(valuesMock.value)
|
||||
})
|
||||
|
||||
vi.mock('vee-validate', () => ({
|
||||
useForm: () => ({
|
||||
handleSubmit: handleSubmitMock,
|
||||
setFieldValue: setFieldValueMock,
|
||||
values: valuesMock,
|
||||
errors: errorsMock,
|
||||
values: valuesMock.value,
|
||||
errors: errorsMock.value,
|
||||
}),
|
||||
}))
|
||||
|
||||
@ -94,13 +97,13 @@ describe('UserName Form', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
valuesMock.username = ''
|
||||
valuesMock.value.username = ''
|
||||
wrapper = mountComponent()
|
||||
})
|
||||
|
||||
describe('when no username is set', () => {
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div#username_form').exists()).toBe(true)
|
||||
expect(wrapper.find('div#username-form').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays the no-username alert', () => {
|
||||
@ -132,8 +135,13 @@ describe('UserName Form', () => {
|
||||
})
|
||||
|
||||
it('enables submit button when a new username is entered', async () => {
|
||||
setFieldValueMock('username', 'newUser')
|
||||
valuesMock.value.username = 'newUser' // Directly set the reactive value
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
// Trigger input change to ensure reactivity
|
||||
await wrapper.find('[data-test="component-input-username"]').trigger('input')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.find('[data-test="submit-username-button"]').exists()).toBe(true)
|
||||
expect(
|
||||
wrapper.find('[data-test="submit-username-button"]').attributes('disabled'),
|
||||
@ -143,7 +151,8 @@ describe('UserName Form', () => {
|
||||
it('submits the form and updates the store on success', async () => {
|
||||
mutationMock.mockResolvedValue({ data: { updateUserInfos: { validValues: 3 } } })
|
||||
|
||||
setFieldValueMock('username', 'newUser')
|
||||
valuesMock.value.username = 'newUser'
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
|
||||
expect(mutationMock).toHaveBeenCalledWith({ alias: 'newUser' })
|
||||
@ -154,7 +163,8 @@ describe('UserName Form', () => {
|
||||
it('shows an error toast on submission failure', async () => {
|
||||
mutationMock.mockRejectedValue(new Error('API Error'))
|
||||
|
||||
setFieldValueMock('username', 'newUser')
|
||||
valuesMock.value.username = 'newUser'
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
|
||||
expect(mutationMock).toHaveBeenCalledWith({ alias: 'newUser' })
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="username_form">
|
||||
<div id="username-form">
|
||||
<div v-if="store.state.username">
|
||||
<label>{{ $t('form.username') }}</label>
|
||||
<BFormGroup
|
||||
@ -21,16 +21,13 @@
|
||||
<BRow class="mb-3">
|
||||
<BCol class="col-12">
|
||||
<input-username
|
||||
:model-value="username"
|
||||
name="username"
|
||||
:placeholder="$t('form.username-placeholder')"
|
||||
show-all-errors
|
||||
:unique="true"
|
||||
:rules="rules"
|
||||
:is-edit="isEdit"
|
||||
data-test="component-input-username"
|
||||
@set-is-edit="setIsEdit(true)"
|
||||
@update:model-value="username = $event"
|
||||
:initial-username-value="username"
|
||||
/>
|
||||
</BCol>
|
||||
<BCol class="col-12">
|
||||
@ -64,7 +61,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useMutation } from '@vue/apollo-composable'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@ -78,9 +75,6 @@ const store = useStore()
|
||||
const { toastError, toastSuccess } = useAppToast()
|
||||
const { t } = useI18n()
|
||||
|
||||
const isEdit = ref(false)
|
||||
const username = ref(store.state.username || '')
|
||||
const usernameUnique = ref(false)
|
||||
const rules = {
|
||||
required: true,
|
||||
min: 3,
|
||||
@ -103,12 +97,9 @@ const onSubmit = handleSubmit(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
const setIsEdit = (bool) => {
|
||||
username.value = store.state.username
|
||||
isEdit.value = bool
|
||||
}
|
||||
const username = computed(() => store.state.username || '')
|
||||
|
||||
const newUsername = computed(() => username.value !== store.state.username)
|
||||
const newUsername = computed(() => values.username && values.username !== store.state.username)
|
||||
|
||||
const disabled = (err) => {
|
||||
return !newUsername.value || !!Object.keys(err).length
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div class="user-naming-format">
|
||||
<BDropdown v-model="selectedOption">
|
||||
<template #button-content>{{ selectedOptionLabel }}</template>
|
||||
<BDropdown :text="selectedOptionLabel">
|
||||
<BDropdownItem
|
||||
v-for="option in dropdownOptions"
|
||||
:key="option.value"
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useToast } from 'bootstrap-vue-next'
|
||||
import { useToastController } from 'bootstrap-vue-next'
|
||||
|
||||
export function useAppToast() {
|
||||
const { t } = useI18n()
|
||||
const { show } = useToast()
|
||||
const { show } = useToastController()
|
||||
const toastSuccess = (message) => {
|
||||
toast(message, {
|
||||
title: t('success'),
|
||||
|
||||
@ -8,18 +8,39 @@ const constants = {
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
CONFIG_VERSION: {
|
||||
DEFAULT: 'DEFAULT',
|
||||
EXPECTED: 'v6.2024-02-27',
|
||||
EXPECTED: 'v7.2024-08-06',
|
||||
CURRENT: '',
|
||||
},
|
||||
}
|
||||
|
||||
const version = {
|
||||
FRONTEND_MODULE_PROTOCOL: process.env.FRONTEND_MODULE_PROTOCOL ?? 'http',
|
||||
FRONTEND_MODULE_HOST: process.env.FRONTEND_MODULE_HOST ?? '0.0.0.0',
|
||||
FRONTEND_MODULE_PORT: process.env.FRONTEND_MODULE_PORT ?? '3000',
|
||||
APP_VERSION: pkg.version,
|
||||
BUILD_COMMIT: process.env.BUILD_COMMIT ?? null,
|
||||
// self reference of `version.BUILD_COMMIT` is not possible at this point, hence the duplicate code
|
||||
BUILD_COMMIT_SHORT: (process.env.BUILD_COMMIT ?? '0000000').slice(0, 7),
|
||||
}
|
||||
|
||||
let FRONTEND_MODULE_URL
|
||||
// in case of hosting the frontend module with a nodejs-instance
|
||||
if (process.env.FRONTEND_HOSTING === 'nodejs') {
|
||||
FRONTEND_MODULE_URL =
|
||||
version.FRONTEND_MODULE_PROTOCOL +
|
||||
'://' +
|
||||
version.FRONTEND_MODULE_HOST +
|
||||
':' +
|
||||
version.FRONTEND_MODULE_PORT
|
||||
} else {
|
||||
// in case of hosting the frontend module with a nginx
|
||||
FRONTEND_MODULE_URL = version.FRONTEND_MODULE_PROTOCOL + '://' + version.FRONTEND_MODULE_HOST
|
||||
}
|
||||
|
||||
// const FRONTEND_MODULE_URI = version.FRONTEND_MODULE_PROTOCOL + '://' + version.FRONTEND_MODULE_HOST // +
|
||||
// ':' +
|
||||
// version.FRONTEND_MODULE_PORT
|
||||
|
||||
const features = {
|
||||
GMS_ACTIVE: process.env.GMS_ACTIVE ?? false,
|
||||
HUMHUB_ACTIVE: process.env.HUMHUB_ACTIVE ?? false,
|
||||
@ -30,16 +51,16 @@ const environment = {
|
||||
DEBUG: process.env.NODE_ENV !== 'production' ?? false,
|
||||
PRODUCTION: process.env.NODE_ENV === 'production' ?? false,
|
||||
DEFAULT_PUBLISHER_ID: process.env.DEFAULT_PUBLISHER_ID ?? 2896,
|
||||
PORT: process.env.PORT ?? 3000,
|
||||
}
|
||||
|
||||
const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? 'localhost'
|
||||
const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http'
|
||||
const COMMUNITY_URL = process.env.COMMUNITY_URL ?? `${URL_PROTOCOL}://${COMMUNITY_HOST}`
|
||||
// const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? 'localhost'
|
||||
// const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http'
|
||||
const COMMUNITY_URL = process.env.COMMUNITY_URL ?? FRONTEND_MODULE_URL
|
||||
|
||||
const endpoints = {
|
||||
GRAPHQL_URI: COMMUNITY_URL + (process.env.GRAPHQL_PATH ?? '/graphql'),
|
||||
GRAPHQL_URI: process.env.GRAPHQL_URI ?? COMMUNITY_URL + (process.env.GRAPHQL_PATH ?? '/graphql'),
|
||||
ADMIN_AUTH_URL:
|
||||
process.env.ADMIN_AUTH_URL ??
|
||||
COMMUNITY_URL + (process.env.ADMIN_AUTH_PATH ?? '/admin/authenticate?token={token}'),
|
||||
}
|
||||
|
||||
@ -94,4 +115,4 @@ const CONFIG = {
|
||||
...meta,
|
||||
}
|
||||
|
||||
export default CONFIG
|
||||
module.exports = CONFIG
|
||||
|
||||
@ -137,7 +137,13 @@ const computedKeyFromForm = computed(() => {
|
||||
return `${form.value.id}_${form.value.date}_${form.value.memo}_${form.value.amount}_${form.value.hours}`
|
||||
})
|
||||
|
||||
const { onResult: onOpenCreationsResult, refetch: refetchOpenCreations } = useQuery(openCreations)
|
||||
const { onResult: onOpenCreationsResult, refetch: refetchOpenCreations } = useQuery(
|
||||
openCreations,
|
||||
() => ({}),
|
||||
{
|
||||
fetchPolicy: 'network-only',
|
||||
},
|
||||
)
|
||||
const { onResult: onListAllContributionsResult, refetch: refetchAllContributions } = useQuery(
|
||||
listAllContributions,
|
||||
() => ({
|
||||
|
||||
@ -130,18 +130,32 @@ describe('Register', () => {
|
||||
})
|
||||
|
||||
it('displays a message that firstname is required', async () => {
|
||||
// First set some value to make the field dirty
|
||||
await wrapper.find('#registerFirstname').setValue('test')
|
||||
await wrapper.find('#registerFirstname').trigger('blur')
|
||||
|
||||
// Then clear it to trigger validation
|
||||
await wrapper.find('#registerFirstname').setValue('')
|
||||
await wrapper.find('#registerFirstname').trigger('blur')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find('#registerFirstnameLiveFeedback').exists()).toBe(true)
|
||||
expect(wrapper.find('#registerFirstnameLiveFeedback').text()).toBe(
|
||||
'The field firstname is invalid',
|
||||
)
|
||||
})
|
||||
|
||||
it('displays a message that lastname is required', async () => {
|
||||
// First set some value to make the field dirty
|
||||
await wrapper.find('#registerLastname').setValue('test')
|
||||
await wrapper.find('#registerLastname').trigger('blur')
|
||||
|
||||
// Then clear it to trigger validation
|
||||
await wrapper.find('#registerLastname').setValue('')
|
||||
await wrapper.find('#registerLastname').trigger('blur')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find('#registerLastnameLiveFeedback').exists()).toBe(true)
|
||||
expect(wrapper.find('#registerLastnameLiveFeedback').text()).toBe(
|
||||
'The field lastname is invalid',
|
||||
)
|
||||
|
||||
@ -94,7 +94,7 @@
|
||||
</BCol>
|
||||
<BCol cols="12" md="6" lg="6" class="text-end">
|
||||
<user-settings-switch
|
||||
:initial-value="$store.state.humhubAllowed"
|
||||
:initial-value="state.humhubAllowed"
|
||||
:attr-name="'humhubAllowed'"
|
||||
:disabled="isHumhubActivated"
|
||||
:enabled-text="$t('settings.humhub.enabled')"
|
||||
@ -111,7 +111,7 @@
|
||||
</BCol>
|
||||
<BCol cols="12" md="6" lg="6">
|
||||
<user-naming-format
|
||||
:initial-value="$store.state.humhubPublishName"
|
||||
:initial-value="state.humhubPublishName"
|
||||
:attr-name="'humhubPublishName'"
|
||||
:success-message="$t('settings.humhub.publish-name.updated')"
|
||||
/>
|
||||
@ -125,7 +125,7 @@
|
||||
</BCol>
|
||||
<BCol cols="12" md="6" lg="6" class="text-end">
|
||||
<user-settings-switch
|
||||
:initial-value="$store.state.gmsAllowed"
|
||||
:initial-value="state.gmsAllowed"
|
||||
:attr-name="'gmsAllowed'"
|
||||
:enabled-text="$t('settings.GMS.enabled')"
|
||||
:disabled-text="$t('settings.GMS.disabled')"
|
||||
@ -141,7 +141,7 @@
|
||||
</BCol>
|
||||
<BCol cols="12" md="6" lg="6">
|
||||
<user-naming-format
|
||||
:initial-value="$store.state.gmsPublishName"
|
||||
:initial-value="state.gmsPublishName"
|
||||
:attr-name="'gmsPublishName'"
|
||||
:success-message="$t('settings.GMS.publish-name.updated')"
|
||||
/>
|
||||
|
||||
@ -9,18 +9,34 @@ import EnvironmentPlugin from 'vite-plugin-environment'
|
||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||
|
||||
import { BootstrapVueNextResolver } from 'bootstrap-vue-next'
|
||||
import CONFIG from './src/config'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
dotenv.config() // load env vars from .env
|
||||
|
||||
const CONFIG = require('./src/config')
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 3000,
|
||||
host: CONFIG.FRONTEND_MODULE_HOST, // '0.0.0.0',
|
||||
port: CONFIG.FRONTEND_MODULE_PORT, // 3000,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
assets: path.join(__dirname, 'src/assets'),
|
||||
'@vee-validate/i18n/dist/locale/en.json':
|
||||
'/node_modules/@vee-validate/i18n/dist/locale/en.json',
|
||||
'@vee-validate/i18n/dist/locale/de.json':
|
||||
'/node_modules/@vee-validate/i18n/dist/locale/de.json',
|
||||
'@vee-validate/i18n/dist/locale/es.json':
|
||||
'/node_modules/@vee-validate/i18n/dist/locale/es.json',
|
||||
'@vee-validate/i18n/dist/locale/fr.json':
|
||||
'/node_modules/@vee-validate/i18n/dist/locale/fr.json',
|
||||
'@vee-validate/i18n/dist/locale/nl.json':
|
||||
'/node_modules/@vee-validate/i18n/dist/locale/nl.json',
|
||||
'@vee-validate/i18n/dist/locale/tr.json':
|
||||
'/node_modules/@vee-validate/i18n/dist/locale/tr.json',
|
||||
},
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
|
||||
},
|
||||
@ -60,9 +76,12 @@ export default defineConfig({
|
||||
URL_PROTOCOL: null,
|
||||
COMMUNITY_URL: null,
|
||||
GRAPHQL_PATH: null,
|
||||
ADMIN_AUTH_PATH: null,
|
||||
GRAPHQL_URI: CONFIG.GRAPHQL_URI, // null,
|
||||
ADMIN_AUTH_PATH: CONFIG.ADMIN_AUTH_PATH ?? null, // it is the only env without exported default
|
||||
ADMIN_AUTH_URL: CONFIG.ADMIN_AUTH_URL, // null,
|
||||
COMMUNITY_NAME: null,
|
||||
COMMUNITY_REGISTER_PATH: null,
|
||||
COMMUNITY_REGISTER_URL: null,
|
||||
COMMUNITY_DESCRIPTION: null,
|
||||
COMMUNITY_SUPPORT_MAIL: null,
|
||||
META_URL: null,
|
||||
@ -87,5 +106,6 @@ export default defineConfig({
|
||||
},
|
||||
build: {
|
||||
outDir: path.resolve(__dirname, './build'),
|
||||
chunkSizeWarningLimit: 1600,
|
||||
},
|
||||
})
|
||||
|
||||
4220
frontend/yarn.lock
4220
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user