mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' into chat-message-notification-e2e-tests
This commit is contained in:
commit
9b989431f2
32
CHANGELOG.md
32
CHANGELOG.md
@ -4,8 +4,40 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [3.5.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.4.0...3.5.0)
|
||||
|
||||
- feat(webapp): user teaser popover [`#8450`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8450)
|
||||
- feat(backend): signup email localized [`#8459`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8459)
|
||||
- lint json [`#8472`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8472)
|
||||
- refactor(other): cypress: simplify cucumber preprocessor imports and some linting [`#8489`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8489)
|
||||
- fix backend node23 [`#8488`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8488)
|
||||
- refactor(workflow): parallelize e2e preparation [`#8481`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8481)
|
||||
- refactor(backend): types for context + `slug` [`#8486`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8486)
|
||||
- feat(backend): emails for notifications [`#8435`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8435)
|
||||
- remove some dependabot groups & no alpine version to allow update [`#8475`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8475)
|
||||
- build(deps-dev): bump the babel group with 3 updates [`#8478`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8478)
|
||||
- build(deps-dev): bump @types/node from 22.15.2 to 22.15.3 in /backend [`#8479`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8479)
|
||||
- refactor(backend): refactor context [`#8434`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8434)
|
||||
- build(deps): bump amannn/action-semantic-pull-request [`#8480`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8480)
|
||||
- build(deps-dev): bump eslint-plugin-prettier in /webapp [`#8332`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8332)
|
||||
- remove all helpers on src/helpers [`#8469`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8469)
|
||||
- move models into database folder [`#8471`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8471)
|
||||
- also lint cjs files [`#8467`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8467)
|
||||
- refactor(backend): refactor badges [`#8465`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8465)
|
||||
- refactor(backend): move resolvers into graphql folder [`#8470`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8470)
|
||||
- refactor(webapp): remove unused packages [`#8468`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8468)
|
||||
- refactor(backend): remove unused packages [`#8466`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8466)
|
||||
- fix(backend): fix backend dev and dev:debug command [`#8439`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8439)
|
||||
- move distanceToMe onto Location [`#8464`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8464)
|
||||
- feat(backend): distanceToMe [`#8462`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8462)
|
||||
- fix(webapp): fixed padding for mobile in basic layout [`#8455`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8455)
|
||||
- Fix ocelot.social link for imprint and donation [`#8461`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8461)
|
||||
|
||||
#### [3.4.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.3.0...3.4.0)
|
||||
|
||||
> 28 April 2025
|
||||
|
||||
- v3.4.0 [`#8454`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8454)
|
||||
- fix(webapp): fix badge focus [`#8452`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8452)
|
||||
- feat(backend): branding middlewares [`#8429`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8429)
|
||||
- refactor(webapp): make login, registration, password-reset layout brandable [`#8440`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8440)
|
||||
|
||||
@ -223,5 +223,10 @@ module.exports = {
|
||||
'jest/unbound-method': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: ['plugin:jsonc/recommended-with-jsonc'],
|
||||
files: ['*.json', '*.json5', '*.jsonc'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-backend",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"description": "GraphQL Backend for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
@ -12,7 +12,7 @@
|
||||
"build": "tsc && tsc-alias && ./scripts/build.copy.files.sh",
|
||||
"dev": "nodemon --exec ts-node --require tsconfig-paths/register src/index.ts -e js,ts,gql",
|
||||
"dev:debug": "nodemon --exec node --inspect=0.0.0.0:9229 build/src/index.js -e js,ts,gql",
|
||||
"lint": "eslint --max-warnings=0 --report-unused-disable-directives --ext .js,.ts,.cjs .",
|
||||
"lint": "eslint --max-warnings=0 --report-unused-disable-directives --ext .js,.ts,.cjs,.json,.json5,.jsonc .",
|
||||
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles",
|
||||
"db:reset": "ts-node --require tsconfig-paths/register src/db/reset.ts",
|
||||
"db:reset:withmigrations": "ts-node --require tsconfig-paths/register src/db/reset-with-migrations.ts",
|
||||
@ -106,6 +106,7 @@
|
||||
"eslint-import-resolver-typescript": "^4.3.4",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest": "^28.11.0",
|
||||
"eslint-plugin-jsonc": "^2.20.0",
|
||||
"eslint-plugin-n": "^17.17.0",
|
||||
"eslint-plugin-no-catch-all": "^1.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
|
||||
@ -1,257 +1,234 @@
|
||||
[
|
||||
{
|
||||
"provider_name": "Codepen",
|
||||
"provider_url": "https:\/\/codepen.io",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http:\/\/codepen.io\/*",
|
||||
"https:\/\/codepen.io\/*"
|
||||
],
|
||||
"url": "http:\/\/codepen.io\/api\/oembed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "DTube",
|
||||
"provider_url": "https:\/\/d.tube\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https:\/\/d.tube\/v\/*"
|
||||
],
|
||||
"url": "https:\/\/api.d.tube\/oembed",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Facebook (Post)",
|
||||
"provider_url": "https:\/\/www.facebook.com\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https:\/\/www.facebook.com\/*\/posts\/*",
|
||||
"https:\/\/www.facebook.com\/photos\/*",
|
||||
"https:\/\/www.facebook.com\/*\/photos\/*",
|
||||
"https:\/\/www.facebook.com\/photo.php*",
|
||||
"https:\/\/www.facebook.com\/photo.php",
|
||||
"https:\/\/www.facebook.com\/*\/activity\/*",
|
||||
"https:\/\/www.facebook.com\/permalink.php",
|
||||
"https:\/\/www.facebook.com\/media\/set?set=*",
|
||||
"https:\/\/www.facebook.com\/questions\/*",
|
||||
"https:\/\/www.facebook.com\/notes\/*\/*\/*"
|
||||
],
|
||||
"url": "https:\/\/www.facebook.com\/plugins\/post\/oembed.json",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Facebook (Video)",
|
||||
"provider_url": "https:\/\/www.facebook.com\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https:\/\/www.facebook.com\/*\/videos\/*",
|
||||
"https:\/\/www.facebook.com\/video.php"
|
||||
],
|
||||
"url": "https:\/\/www.facebook.com\/plugins\/video\/oembed.json",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Flickr",
|
||||
"provider_url": "https:\/\/www.flickr.com\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http:\/\/*.flickr.com\/photos\/*",
|
||||
"http:\/\/flic.kr\/p\/*",
|
||||
"https:\/\/*.flickr.com\/photos\/*",
|
||||
"https:\/\/flic.kr\/p\/*"
|
||||
],
|
||||
"url": "https:\/\/www.flickr.com\/services\/oembed\/",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "GIPHY",
|
||||
"provider_url": "https:\/\/giphy.com",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https:\/\/giphy.com\/gifs\/*",
|
||||
"http:\/\/gph.is\/*",
|
||||
"https:\/\/media.giphy.com\/media\/*\/giphy.gif"
|
||||
],
|
||||
"url": "https:\/\/giphy.com\/services\/oembed",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Instagram",
|
||||
"provider_url": "https:\/\/instagram.com",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http:\/\/instagram.com\/p\/*",
|
||||
"http:\/\/instagr.am\/p\/*",
|
||||
"http:\/\/www.instagram.com\/p\/*",
|
||||
"http:\/\/www.instagr.am\/p\/*",
|
||||
"https:\/\/instagram.com\/p\/*",
|
||||
"https:\/\/instagr.am\/p\/*",
|
||||
"https:\/\/www.instagram.com\/p\/*",
|
||||
"https:\/\/www.instagr.am\/p\/*"
|
||||
],
|
||||
"url": "https:\/\/api.instagram.com\/oembed",
|
||||
"formats": [
|
||||
"json"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Meetup",
|
||||
"provider_url": "http:\/\/www.meetup.com",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http:\/\/meetup.com\/*",
|
||||
"https:\/\/www.meetup.com\/*",
|
||||
"https:\/\/meetup.com\/*",
|
||||
"http:\/\/meetu.ps\/*"
|
||||
],
|
||||
"url": "https:\/\/api.meetup.com\/oembed",
|
||||
"formats": [
|
||||
"json"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "MixCloud",
|
||||
"provider_url": "https:\/\/mixcloud.com\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http:\/\/www.mixcloud.com\/*\/*\/",
|
||||
"https:\/\/www.mixcloud.com\/*\/*\/"
|
||||
],
|
||||
"url": "https:\/\/www.mixcloud.com\/oembed\/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Reddit",
|
||||
"provider_url": "https:\/\/reddit.com\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https:\/\/reddit.com\/r\/*\/comments\/*\/*",
|
||||
"https:\/\/www.reddit.com\/r\/*\/comments\/*\/*"
|
||||
],
|
||||
"url": "https:\/\/www.reddit.com\/oembed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "SlideShare",
|
||||
"provider_url": "http:\/\/www.slideshare.net\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http:\/\/www.slideshare.net\/*\/*",
|
||||
"http:\/\/fr.slideshare.net\/*\/*",
|
||||
"http:\/\/de.slideshare.net\/*\/*",
|
||||
"http:\/\/es.slideshare.net\/*\/*",
|
||||
"http:\/\/pt.slideshare.net\/*\/*"
|
||||
],
|
||||
"url": "http:\/\/www.slideshare.net\/api\/oembed\/2",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "SoundCloud",
|
||||
"provider_url": "http:\/\/soundcloud.com\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http:\/\/soundcloud.com\/*",
|
||||
"https:\/\/soundcloud.com\/*"
|
||||
],
|
||||
"url": "https:\/\/soundcloud.com\/oembed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Twitch",
|
||||
"provider_url": "https:\/\/www.twitch.tv",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http:\/\/clips.twitch.tv\/*",
|
||||
"https:\/\/clips.twitch.tv\/*",
|
||||
"http:\/\/www.twitch.tv\/*",
|
||||
"https:\/\/www.twitch.tv\/*",
|
||||
"http:\/\/twitch.tv\/*",
|
||||
"https:\/\/twitch.tv\/*"
|
||||
],
|
||||
"url": "https:\/\/api.twitch.tv\/v4\/oembed",
|
||||
"formats": [
|
||||
"json"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Twitter",
|
||||
"provider_url": "http:\/\/www.twitter.com\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https:\/\/twitter.com\/*\/status\/*",
|
||||
"https:\/\/*.twitter.com\/*\/status\/*"
|
||||
],
|
||||
"url": "https:\/\/publish.twitter.com\/oembed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Vimeo",
|
||||
"provider_url": "https:\/\/vimeo.com\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https:\/\/vimeo.com\/*",
|
||||
"https:\/\/vimeo.com\/album\/*\/video\/*",
|
||||
"https:\/\/vimeo.com\/channels\/*\/*",
|
||||
"https:\/\/vimeo.com\/groups\/*\/videos\/*",
|
||||
"https:\/\/vimeo.com\/ondemand\/*\/*",
|
||||
"https:\/\/player.vimeo.com\/video\/*"
|
||||
],
|
||||
"url": "https:\/\/vimeo.com\/api\/oembed.{format}",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "YouTube",
|
||||
"provider_url": "https:\/\/www.youtube.com\/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https:\/\/*.youtube.com\/watch*",
|
||||
"https:\/\/*.youtube.com\/v\/*",
|
||||
"https:\/\/youtu.be\/*"
|
||||
],
|
||||
"url": "https:\/\/www.youtube.com\/oembed",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
{
|
||||
"provider_name": "Codepen",
|
||||
"provider_url": "https://codepen.io",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": ["http://codepen.io/*", "https://codepen.io/*"],
|
||||
"url": "http://codepen.io/api/oembed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "DTube",
|
||||
"provider_url": "https://d.tube/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": ["https://d.tube/v/*"],
|
||||
"url": "https://api.d.tube/oembed",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Facebook (Post)",
|
||||
"provider_url": "https://www.facebook.com/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https://www.facebook.com/*/posts/*",
|
||||
"https://www.facebook.com/photos/*",
|
||||
"https://www.facebook.com/*/photos/*",
|
||||
"https://www.facebook.com/photo.php*",
|
||||
"https://www.facebook.com/photo.php",
|
||||
"https://www.facebook.com/*/activity/*",
|
||||
"https://www.facebook.com/permalink.php",
|
||||
"https://www.facebook.com/media/set?set=*",
|
||||
"https://www.facebook.com/questions/*",
|
||||
"https://www.facebook.com/notes/*/*/*"
|
||||
],
|
||||
"url": "https://www.facebook.com/plugins/post/oembed.json",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Facebook (Video)",
|
||||
"provider_url": "https://www.facebook.com/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": ["https://www.facebook.com/*/videos/*", "https://www.facebook.com/video.php"],
|
||||
"url": "https://www.facebook.com/plugins/video/oembed.json",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Flickr",
|
||||
"provider_url": "https://www.flickr.com/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http://*.flickr.com/photos/*",
|
||||
"http://flic.kr/p/*",
|
||||
"https://*.flickr.com/photos/*",
|
||||
"https://flic.kr/p/*"
|
||||
],
|
||||
"url": "https://www.flickr.com/services/oembed/",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "GIPHY",
|
||||
"provider_url": "https://giphy.com",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https://giphy.com/gifs/*",
|
||||
"http://gph.is/*",
|
||||
"https://media.giphy.com/media/*/giphy.gif"
|
||||
],
|
||||
"url": "https://giphy.com/services/oembed",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Instagram",
|
||||
"provider_url": "https://instagram.com",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http://instagram.com/p/*",
|
||||
"http://instagr.am/p/*",
|
||||
"http://www.instagram.com/p/*",
|
||||
"http://www.instagr.am/p/*",
|
||||
"https://instagram.com/p/*",
|
||||
"https://instagr.am/p/*",
|
||||
"https://www.instagram.com/p/*",
|
||||
"https://www.instagr.am/p/*"
|
||||
],
|
||||
"url": "https://api.instagram.com/oembed",
|
||||
"formats": ["json"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Meetup",
|
||||
"provider_url": "http://www.meetup.com",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http://meetup.com/*",
|
||||
"https://www.meetup.com/*",
|
||||
"https://meetup.com/*",
|
||||
"http://meetu.ps/*"
|
||||
],
|
||||
"url": "https://api.meetup.com/oembed",
|
||||
"formats": ["json"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "MixCloud",
|
||||
"provider_url": "https://mixcloud.com/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": ["http://www.mixcloud.com/*/*/", "https://www.mixcloud.com/*/*/"],
|
||||
"url": "https://www.mixcloud.com/oembed/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Reddit",
|
||||
"provider_url": "https://reddit.com/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https://reddit.com/r/*/comments/*/*",
|
||||
"https://www.reddit.com/r/*/comments/*/*"
|
||||
],
|
||||
"url": "https://www.reddit.com/oembed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "SlideShare",
|
||||
"provider_url": "http://www.slideshare.net/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http://www.slideshare.net/*/*",
|
||||
"http://fr.slideshare.net/*/*",
|
||||
"http://de.slideshare.net/*/*",
|
||||
"http://es.slideshare.net/*/*",
|
||||
"http://pt.slideshare.net/*/*"
|
||||
],
|
||||
"url": "http://www.slideshare.net/api/oembed/2",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "SoundCloud",
|
||||
"provider_url": "http://soundcloud.com/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": ["http://soundcloud.com/*", "https://soundcloud.com/*"],
|
||||
"url": "https://soundcloud.com/oembed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Twitch",
|
||||
"provider_url": "https://www.twitch.tv",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"http://clips.twitch.tv/*",
|
||||
"https://clips.twitch.tv/*",
|
||||
"http://www.twitch.tv/*",
|
||||
"https://www.twitch.tv/*",
|
||||
"http://twitch.tv/*",
|
||||
"https://twitch.tv/*"
|
||||
],
|
||||
"url": "https://api.twitch.tv/v4/oembed",
|
||||
"formats": ["json"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Twitter",
|
||||
"provider_url": "http://www.twitter.com/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": ["https://twitter.com/*/status/*", "https://*.twitter.com/*/status/*"],
|
||||
"url": "https://publish.twitter.com/oembed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "Vimeo",
|
||||
"provider_url": "https://vimeo.com/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https://vimeo.com/*",
|
||||
"https://vimeo.com/album/*/video/*",
|
||||
"https://vimeo.com/channels/*/*",
|
||||
"https://vimeo.com/groups/*/videos/*",
|
||||
"https://vimeo.com/ondemand/*/*",
|
||||
"https://player.vimeo.com/video/*"
|
||||
],
|
||||
"url": "https://vimeo.com/api/oembed.{format}",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider_name": "YouTube",
|
||||
"provider_url": "https://www.youtube.com/",
|
||||
"endpoints": [
|
||||
{
|
||||
"schemes": [
|
||||
"https://*.youtube.com/watch*",
|
||||
"https://*.youtube.com/v/*",
|
||||
"https://youtu.be/*"
|
||||
],
|
||||
"url": "https://www.youtube.com/oembed",
|
||||
"discovery": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@ -62,6 +62,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -185,6 +189,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
|
||||
@ -0,0 +1,261 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`sendEmailVerification English renders correctly 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Hello User,</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>So, you want to change your e-mail? No problem! Just click the button below to verify your new address:</p><a class="button" href="http://webapp:3000/settings/my-email-address/verify?email=user%40example.org&nonce=123456">Verify e-mail address</a>
|
||||
<p>If you don't want to change your e-mail address feel free to ignore this message. </p>
|
||||
<p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></p>
|
||||
<div class="text-block">
|
||||
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– The ocelot.social Team</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "New E-Mail Address ocelot.social",
|
||||
"text": "HELLO USER,
|
||||
|
||||
So, you want to change your e-mail? No problem! Just click the button below to
|
||||
verify your new address:
|
||||
|
||||
Verify e-mail address
|
||||
[http://webapp:3000/settings/my-email-address/verify?email=user%40example.org&nonce=123456]
|
||||
|
||||
If you don't want to change your e-mail address feel free to ignore this
|
||||
message.
|
||||
|
||||
If the above button doesn't work, you can also copy the following code into your
|
||||
browser window: 123456
|
||||
|
||||
See you soon on ocelot.social [https://ocelot.social]!
|
||||
|
||||
– The ocelot.social Team
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`sendEmailVerification German renders correctly 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Hallo User,</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>Du möchtest also deine E-Mail ändern? Kein Problem! Mit Klick auf diesen Button kannst Du Deine neue E-Mail Adresse bestätigen:</p><a class="button" href="http://webapp:3000/settings/my-email-address/verify?email=user%40example.org&nonce=123456">E-Mail Adresse bestätigen</a>
|
||||
<p>Falls Du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese Nachricht einfach ignorieren. </p>
|
||||
<p>Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: <span>123456</span></p>
|
||||
<div class="text-block">
|
||||
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– Dein ocelot.social Team</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "Neue E-Mail Addresse ocelot.social",
|
||||
"text": "HALLO USER,
|
||||
|
||||
Du möchtest also deine E-Mail ändern? Kein Problem! Mit Klick auf diesen Button
|
||||
kannst Du Deine neue E-Mail Adresse bestätigen:
|
||||
|
||||
E-Mail Adresse bestätigen
|
||||
[http://webapp:3000/settings/my-email-address/verify?email=user%40example.org&nonce=123456]
|
||||
|
||||
Falls Du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese
|
||||
Nachricht einfach ignorieren.
|
||||
|
||||
Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in
|
||||
Dein Browserfenster kopieren: 123456
|
||||
|
||||
Bis bald bei ocelot.social [https://ocelot.social]!
|
||||
|
||||
– Dein ocelot.social Team
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
@ -62,6 +62,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -184,6 +188,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -308,6 +316,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -432,6 +444,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -556,6 +572,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -679,6 +699,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -801,6 +825,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -920,6 +948,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -1043,6 +1075,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -1166,6 +1202,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -1288,6 +1328,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -1412,6 +1456,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -1536,6 +1584,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -1660,6 +1712,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -1783,6 +1839,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -1905,6 +1965,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -2024,6 +2088,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
@ -2147,6 +2215,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
|
||||
@ -0,0 +1,559 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`sendRegistrationMail with invite code English renders correctly 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Welcome to ocelot.social!</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>Thank you for joining our cause – it's awesome to have you on board. There's just one tiny step missing before we can start shaping the world together … Please confirm your e-mail address by clicking the button below:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&nonce=123456&inviteCode=welcome&method=invite-code">Confirm your e-mail address</a>
|
||||
<p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></p>
|
||||
<p>However, this only works if you have registered through our website.</p>
|
||||
<p>If you didn't sign up for <a>ocelot.social</a> we recommend you to check it out! It's a social network from people for people who want to connect and change the world together.
|
||||
</p>
|
||||
<p>PS: If you ignore this e-mail we will not create an account for you. ;)</p>
|
||||
<div class="text-block">
|
||||
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– The ocelot.social Team</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "Welcome to ocelot.social",
|
||||
"text": "WELCOME TO OCELOT.SOCIAL!
|
||||
|
||||
Thank you for joining our cause – it's awesome to have you on board. There's
|
||||
just one tiny step missing before we can start shaping the world together …
|
||||
Please confirm your e-mail address by clicking the button below:
|
||||
|
||||
Confirm your e-mail address
|
||||
[http://webapp:3000/registration?email=user%40example.org&nonce=123456&inviteCode=welcome&method=invite-code]
|
||||
|
||||
If the above button doesn't work, you can also copy the following code into your
|
||||
browser window: 123456
|
||||
|
||||
However, this only works if you have registered through our website.
|
||||
|
||||
If you didn't sign up for ocelot.social we recommend you to check it out! It's a
|
||||
social network from people for people who want to connect and change the world
|
||||
together.
|
||||
|
||||
PS: If you ignore this e-mail we will not create an account for you. ;)
|
||||
|
||||
See you soon on ocelot.social [https://ocelot.social]!
|
||||
|
||||
– The ocelot.social Team
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`sendRegistrationMail with invite code German renders correctly 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Willkommen bei ocelot.social!</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>Danke, dass du dich angemeldet hast – wir freuen uns, dich dabei zu haben. Jetzt fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können … Bitte bestätige Deine E-Mail Adresse:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&nonce=123456&inviteCode=welcome&method=invite-code">Bestätige Deine E-Mail Adresse</a>
|
||||
<p>Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: <span>123456</span></p>
|
||||
<p>Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert hast.</p>
|
||||
<p>Falls Du Dich nicht selbst bei <a>ocelot.social</a> angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk – von Menschen für Menschen.
|
||||
</p>
|
||||
<p>PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese E-Mail einfach ignorieren. ;)</p>
|
||||
<div class="text-block">
|
||||
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– Dein ocelot.social Team</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "Willkommen bei ocelot.social",
|
||||
"text": "WILLKOMMEN BEI OCELOT.SOCIAL!
|
||||
|
||||
Danke, dass du dich angemeldet hast – wir freuen uns, dich dabei zu haben. Jetzt
|
||||
fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können
|
||||
… Bitte bestätige Deine E-Mail Adresse:
|
||||
|
||||
Bestätige Deine E-Mail Adresse
|
||||
[http://webapp:3000/registration?email=user%40example.org&nonce=123456&inviteCode=welcome&method=invite-code]
|
||||
|
||||
Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in
|
||||
Dein Browserfenster kopieren: 123456
|
||||
|
||||
Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert
|
||||
hast.
|
||||
|
||||
Falls Du Dich nicht selbst bei ocelot.social angemeldet hast, schau doch mal
|
||||
vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk – von Menschen für Menschen.
|
||||
|
||||
PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese E-Mail einfach
|
||||
ignorieren. ;)
|
||||
|
||||
Bis bald bei ocelot.social [https://ocelot.social]!
|
||||
|
||||
– Dein ocelot.social Team
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`sendRegistrationMail without invite code English renders correctly 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Welcome to ocelot.social!</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>Thank you for joining our cause – it's awesome to have you on board. There's just one tiny step missing before we can start shaping the world together … Please confirm your e-mail address by clicking the button below:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&nonce=123456&method=invite-mail">Confirm your e-mail address</a>
|
||||
<p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></p>
|
||||
<p>However, this only works if you have registered through our website.</p>
|
||||
<p>If you didn't sign up for <a>ocelot.social</a> we recommend you to check it out! It's a social network from people for people who want to connect and change the world together.
|
||||
</p>
|
||||
<p>PS: If you ignore this e-mail we will not create an account for you. ;)</p>
|
||||
<div class="text-block">
|
||||
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– The ocelot.social Team</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "Welcome to ocelot.social",
|
||||
"text": "WELCOME TO OCELOT.SOCIAL!
|
||||
|
||||
Thank you for joining our cause – it's awesome to have you on board. There's
|
||||
just one tiny step missing before we can start shaping the world together …
|
||||
Please confirm your e-mail address by clicking the button below:
|
||||
|
||||
Confirm your e-mail address
|
||||
[http://webapp:3000/registration?email=user%40example.org&nonce=123456&method=invite-mail]
|
||||
|
||||
If the above button doesn't work, you can also copy the following code into your
|
||||
browser window: 123456
|
||||
|
||||
However, this only works if you have registered through our website.
|
||||
|
||||
If you didn't sign up for ocelot.social we recommend you to check it out! It's a
|
||||
social network from people for people who want to connect and change the world
|
||||
together.
|
||||
|
||||
PS: If you ignore this e-mail we will not create an account for you. ;)
|
||||
|
||||
See you soon on ocelot.social [https://ocelot.social]!
|
||||
|
||||
– The ocelot.social Team
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`sendRegistrationMail without invite code German renders correctly 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Willkommen bei ocelot.social!</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>Danke, dass du dich angemeldet hast – wir freuen uns, dich dabei zu haben. Jetzt fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können … Bitte bestätige Deine E-Mail Adresse:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&nonce=123456&method=invite-mail">Bestätige Deine E-Mail Adresse</a>
|
||||
<p>Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: <span>123456</span></p>
|
||||
<p>Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert hast.</p>
|
||||
<p>Falls Du Dich nicht selbst bei <a>ocelot.social</a> angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk – von Menschen für Menschen.
|
||||
</p>
|
||||
<p>PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese E-Mail einfach ignorieren. ;)</p>
|
||||
<div class="text-block">
|
||||
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– Dein ocelot.social Team</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "Willkommen bei ocelot.social",
|
||||
"text": "WILLKOMMEN BEI OCELOT.SOCIAL!
|
||||
|
||||
Danke, dass du dich angemeldet hast – wir freuen uns, dich dabei zu haben. Jetzt
|
||||
fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können
|
||||
… Bitte bestätige Deine E-Mail Adresse:
|
||||
|
||||
Bestätige Deine E-Mail Adresse
|
||||
[http://webapp:3000/registration?email=user%40example.org&nonce=123456&method=invite-mail]
|
||||
|
||||
Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in
|
||||
Dein Browserfenster kopieren: 123456
|
||||
|
||||
Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert
|
||||
hast.
|
||||
|
||||
Falls Du Dich nicht selbst bei ocelot.social angemeldet hast, schau doch mal
|
||||
vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk – von Menschen für Menschen.
|
||||
|
||||
PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese E-Mail einfach
|
||||
ignorieren. ;)
|
||||
|
||||
Bis bald bei ocelot.social [https://ocelot.social]!
|
||||
|
||||
– Dein ocelot.social Team
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,260 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`sendResetPasswordMail English renders correctly 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Hello Jenny Rostock,</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>So, you forgot your password? No problem! Just click the button below to reset it within the next 24 hours:</p><a class="button" href="http://webapp:3000/password-reset/change-password?email=user%40example.org&nonce=123456">Confirm your e-mail address</a>
|
||||
<p>If you didn't request a new password feel free to ignore this e-mail.</p>
|
||||
<p>If the above button doesn't work you can also copy the following code into your browser window: <span>123456</span></p>
|
||||
<div class="text-block">
|
||||
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– The ocelot.social Team</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "Reset Password ocelot.social",
|
||||
"text": "HELLO JENNY ROSTOCK,
|
||||
|
||||
So, you forgot your password? No problem! Just click the button below to reset
|
||||
it within the next 24 hours:
|
||||
|
||||
Confirm your e-mail address
|
||||
[http://webapp:3000/password-reset/change-password?email=user%40example.org&nonce=123456]
|
||||
|
||||
If you didn't request a new password feel free to ignore this e-mail.
|
||||
|
||||
If the above button doesn't work you can also copy the following code into your
|
||||
browser window: 123456
|
||||
|
||||
See you soon on ocelot.social [https://ocelot.social]!
|
||||
|
||||
– The ocelot.social Team
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`sendResetPasswordMail German renders correctly 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Hallo Jenny Rostock,</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>Du hast also dein Passwort vergessen? Kein Problem! Mit Klick auf diesen Button kannst du innerhalb der nächsten 24 Stunden dein Passwort zurücksetzen:</p><a class="button" href="http://webapp:3000/password-reset/change-password?email=user%40example.org&nonce=123456">Bestätige Deine E-Mail Adresse</a>
|
||||
<p>Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach ignorieren.</p>
|
||||
<p>Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in Dein Browserfenster kopieren: <span>123456</span></p>
|
||||
<div class="text-block">
|
||||
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– Dein ocelot.social Team</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "Neues Passwort ocelot.social",
|
||||
"text": "HALLO JENNY ROSTOCK,
|
||||
|
||||
Du hast also dein Passwort vergessen? Kein Problem! Mit Klick auf diesen Button
|
||||
kannst du innerhalb der nächsten 24 Stunden dein Passwort zurücksetzen:
|
||||
|
||||
Bestätige Deine E-Mail Adresse
|
||||
[http://webapp:3000/password-reset/change-password?email=user%40example.org&nonce=123456]
|
||||
|
||||
Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach
|
||||
ignorieren.
|
||||
|
||||
Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in
|
||||
Dein Browserfenster kopieren: 123456
|
||||
|
||||
Bis bald bei ocelot.social [https://ocelot.social]!
|
||||
|
||||
– Dein ocelot.social Team
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
255
backend/src/emails/__snapshots__/sendWrongEmail.spec.ts.snap
Normal file
255
backend/src/emails/__snapshots__/sendWrongEmail.spec.ts.snap
Normal file
@ -0,0 +1,255 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`sendWrongEmail English renders correctly 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Welcome to ocelot.social!</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>You requested a password reset but unfortunately we couldn't find an account associated with your e-mail address. Did you maybe use another one when you signed up?</p><a class="button" href="http://webapp:3000/password-reset/request">Try a different e-mail</a>
|
||||
<p>If you don't have an account at <a>ocelot.social</a> yet or if you didn't want to reset your password, please ignore this e-mail.
|
||||
</p>
|
||||
<div class="text-block">
|
||||
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– The ocelot.social Team</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "Wrong E-mail? ocelot.social",
|
||||
"text": "WELCOME TO OCELOT.SOCIAL!
|
||||
|
||||
You requested a password reset but unfortunately we couldn't find an account
|
||||
associated with your e-mail address. Did you maybe use another one when you
|
||||
signed up?
|
||||
|
||||
Try a different e-mail [http://webapp:3000/password-reset/request]
|
||||
|
||||
If you don't have an account at ocelot.social yet or if you didn't want to reset
|
||||
your password, please ignore this e-mail.
|
||||
|
||||
See you soon on ocelot.social [https://ocelot.social]!
|
||||
|
||||
– The ocelot.social Team
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`sendWrongEmail German renders correctly 1`] = `
|
||||
{
|
||||
"attachments": [],
|
||||
"from": "ocelot.social",
|
||||
"html": "<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
|
||||
<style>body{
|
||||
display: block;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: left;
|
||||
text-align: -webkit-left;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 25px;
|
||||
font-size: 25px;
|
||||
font-weight: normal;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 60%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background: #17b53e;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 15px;
|
||||
text-decoration: none;
|
||||
text-align:center;
|
||||
padding: 13px 17px;
|
||||
color: #ffffff;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
|
||||
</div>
|
||||
</header>
|
||||
<h2>Willkommen bei ocelot.social!</h2>
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<p>Du hast bei uns ein neues Passwort angefordert – leider haben wir aber keinen Account mit deiner E-Mailadresse gefunden. Kann es sein, dass du mit einer anderen Adresse bei uns angemeldet bist?</p><a class="button" href="http://webapp:3000/password-reset/request">Versuch' es mit einer anderen E-Mail</a>
|
||||
<p>Wenn du noch keinen Account bei <a>ocelot.social</a> hast oder dein Password gar nicht ändern willst, kannst du diese E-Mail einfach ignorieren!
|
||||
</p>
|
||||
<div class="text-block">
|
||||
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
|
||||
<p>– Dein ocelot.social Team</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
"subject": "Falsche Mailaddresse? ocelot.social",
|
||||
"text": "WILLKOMMEN BEI OCELOT.SOCIAL!
|
||||
|
||||
Du hast bei uns ein neues Passwort angefordert – leider haben wir aber keinen
|
||||
Account mit deiner E-Mailadresse gefunden. Kann es sein, dass du mit einer
|
||||
anderen Adresse bei uns angemeldet bist?
|
||||
|
||||
Versuch' es mit einer anderen E-Mail [http://webapp:3000/password-reset/request]
|
||||
|
||||
Wenn du noch keinen Account bei ocelot.social hast oder dein Password gar nicht
|
||||
ändern willst, kannst du diese E-Mail einfach ignorieren!
|
||||
|
||||
Bis bald bei ocelot.social [https://ocelot.social]!
|
||||
|
||||
– Dein ocelot.social Team
|
||||
|
||||
|
||||
ocelot.social Community [https://ocelot.social]",
|
||||
"to": "user@example.org",
|
||||
}
|
||||
`;
|
||||
@ -7,12 +7,32 @@
|
||||
"followedUserPosted": "Neuer Beitrag von gefolgtem Nutzer",
|
||||
"mentionedInComment": "Erwähnung in Kommentar",
|
||||
"mentionedInPost": "Erwähnung in Beitrag",
|
||||
"newEmail": "Neue E-Mail Addresse",
|
||||
"removedUserFromGroup": "Aus Gruppe entfernt",
|
||||
"postInGroup": "Neuer Beitrag in Gruppe",
|
||||
"resetPassword": "Neues Passwort",
|
||||
"userJoinedGroup": "Nutzer tritt Gruppe bei",
|
||||
"userLeftGroup": "Nutzer verlässt Gruppe"
|
||||
"userLeftGroup": "Nutzer verlässt Gruppe",
|
||||
"wrongEmail": "Falsche Mailaddresse?"
|
||||
},
|
||||
"registration": {
|
||||
"introduction": "Danke, dass du dich angemeldet hast – wir freuen uns, dich dabei zu haben. Jetzt fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können … Bitte bestätige Deine E-Mail Adresse:",
|
||||
"codeHint": "Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: ",
|
||||
"codeHintException": "Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert hast.",
|
||||
"notYouStart": "Falls Du Dich nicht selbst bei ",
|
||||
"notYouEnd": " angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk – von Menschen für Menschen.",
|
||||
"ps": "PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese E-Mail einfach ignorieren. ;)"
|
||||
},
|
||||
"emailVerification": {
|
||||
"codeHint": "Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: ",
|
||||
"introduction": "Du möchtest also deine E-Mail ändern? Kein Problem! Mit Klick auf diesen Button kannst Du Deine neue E-Mail Adresse bestätigen:",
|
||||
"doNotChange": "Falls Du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese Nachricht einfach ignorieren. "
|
||||
},
|
||||
"buttons": {
|
||||
"confirmEmail": "Bestätige Deine E-Mail Adresse",
|
||||
"resetPassword": "Passwort zurücksetzen",
|
||||
"tryAgain": "Versuch' es mit einer anderen E-Mail",
|
||||
"verifyEmail": "E-Mail Adresse bestätigen",
|
||||
"viewChat": "Chat anzeigen",
|
||||
"viewComment": "Kommentar ansehen",
|
||||
"viewGroup": "Gruppe ansehen",
|
||||
@ -23,7 +43,19 @@
|
||||
"seeYou": "Bis bald bei ",
|
||||
"yourTeam": "– Dein {team} Team",
|
||||
"settingsHint": "PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine ",
|
||||
"settingsName": "Benachrichtigungseinstellungen"
|
||||
"settingsName": "Benachrichtigungseinstellungen",
|
||||
"welcome": "Willkommen bei"
|
||||
},
|
||||
"resetPassword": {
|
||||
"codeHint": "Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in Dein Browserfenster kopieren: ",
|
||||
"ignore": "Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach ignorieren.",
|
||||
"introduction": "Du hast also dein Passwort vergessen? Kein Problem! Mit Klick auf diesen Button kannst du innerhalb der nächsten 24 Stunden dein Passwort zurücksetzen:"
|
||||
},
|
||||
"wrongEmail": {
|
||||
"codeHint": "Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: ",
|
||||
"ignoreEnd": " hast oder dein Password gar nicht ändern willst, kannst du diese E-Mail einfach ignorieren!",
|
||||
"ignoreStart": "Wenn du noch keinen Account bei ",
|
||||
"introduction": "Du hast bei uns ein neues Passwort angefordert – leider haben wir aber keinen Account mit deiner E-Mailadresse gefunden. Kann es sein, dass du mit einer anderen Adresse bei uns angemeldet bist?"
|
||||
},
|
||||
"changedGroupMemberRole": "deine Rolle in der Gruppe „{groupName}“ wurde geändert. Klicke auf den Knopf, um diese Gruppe zu sehen:",
|
||||
"chatMessageStart": "du hast eine neue Chat-Nachricht von ",
|
||||
|
||||
@ -7,12 +7,32 @@
|
||||
"followedUserPosted": "New post by followd user",
|
||||
"mentionedInComment": "Mentioned in comment",
|
||||
"mentionedInPost": "Mentioned in post",
|
||||
"newEmail": "New E-Mail Address",
|
||||
"removedUserFromGroup": "Removed from group",
|
||||
"postInGroup": "New post in group",
|
||||
"resetPassword": "Reset Password",
|
||||
"userJoinedGroup": "User joined group",
|
||||
"userLeftGroup": "User left group"
|
||||
"userLeftGroup": "User left group",
|
||||
"wrongEmail": "Wrong E-mail?"
|
||||
},
|
||||
"registration": {
|
||||
"introduction": "Thank you for joining our cause – it's awesome to have you on board. There's just one tiny step missing before we can start shaping the world together … Please confirm your e-mail address by clicking the button below:",
|
||||
"codeHint": "If the above button doesn't work, you can also copy the following code into your browser window: ",
|
||||
"codeHintException": "However, this only works if you have registered through our website.",
|
||||
"notYouStart": "If you didn't sign up for ",
|
||||
"notYouEnd": " we recommend you to check it out! It's a social network from people for people who want to connect and change the world together.",
|
||||
"ps": "PS: If you ignore this e-mail we will not create an account for you. ;)"
|
||||
},
|
||||
"emailVerification": {
|
||||
"codeHint": "If the above button doesn't work, you can also copy the following code into your browser window: ",
|
||||
"introduction": "So, you want to change your e-mail? No problem! Just click the button below to verify your new address:",
|
||||
"doNotChange": "If you don't want to change your e-mail address feel free to ignore this message. "
|
||||
},
|
||||
"buttons": {
|
||||
"confirmEmail": "Confirm your e-mail address",
|
||||
"resetPassword": "Reset password",
|
||||
"tryAgain": "Try a different e-mail",
|
||||
"verifyEmail": "Verify e-mail address",
|
||||
"viewChat": "Show Chat",
|
||||
"viewComment": "View comment",
|
||||
"viewGroup": "View group",
|
||||
@ -23,7 +43,18 @@
|
||||
"seeYou": "See you soon on ",
|
||||
"yourTeam": "– The {team} Team",
|
||||
"settingsHint": "PS: If you don't want to receive e-mails anymore, change your ",
|
||||
"settingsName": "notification settings"
|
||||
"settingsName": "notification settings",
|
||||
"welcome": "Welcome to"
|
||||
},
|
||||
"resetPassword": {
|
||||
"codeHint": "If the above button doesn't work you can also copy the following code into your browser window: ",
|
||||
"ignore": "If you didn't request a new password feel free to ignore this e-mail.",
|
||||
"introduction": "So, you forgot your password? No problem! Just click the button below to reset it within the next 24 hours:"
|
||||
},
|
||||
"wrongEmail": {
|
||||
"ignoreEnd": " yet or if you didn't want to reset your password, please ignore this e-mail.",
|
||||
"ignoreStart": "If you don't have an account at ",
|
||||
"introduction": "You requested a password reset but unfortunately we couldn't find an account associated with your e-mail address. Did you maybe use another one when you signed up?"
|
||||
},
|
||||
"changedGroupMemberRole": "your role in the group “{groupName}” has been changed. Click on the button to view this group:",
|
||||
"chatMessageStart": "you have received a new chat message from ",
|
||||
|
||||
@ -28,6 +28,7 @@ const defaultParams = {
|
||||
ORGANIZATION_URL: CONFIG.ORGANIZATION_URL,
|
||||
supportUrl: CONFIG.SUPPORT_URL,
|
||||
settingsUrl,
|
||||
renderSettingsUrl: true,
|
||||
}
|
||||
|
||||
export const transport = createTransport({
|
||||
@ -202,3 +203,137 @@ export const sendChatMessageMail = async (
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
interface VerifyMailInput {
|
||||
email: string
|
||||
nonce: string
|
||||
locale: string
|
||||
}
|
||||
|
||||
interface RegistrationMailInput extends VerifyMailInput {
|
||||
inviteCode?: string
|
||||
}
|
||||
|
||||
export const sendRegistrationMail = async (
|
||||
data: RegistrationMailInput,
|
||||
): Promise<OriginalMessage> => {
|
||||
const { nonce, locale, inviteCode } = data
|
||||
const to = data.email
|
||||
const actionUrl = new URL('/registration', CONFIG.CLIENT_URI)
|
||||
actionUrl.searchParams.set('email', to)
|
||||
actionUrl.searchParams.set('nonce', nonce)
|
||||
if (inviteCode) {
|
||||
actionUrl.searchParams.set('inviteCode', inviteCode)
|
||||
actionUrl.searchParams.set('method', 'invite-code')
|
||||
} else {
|
||||
actionUrl.searchParams.set('method', 'invite-mail')
|
||||
}
|
||||
|
||||
try {
|
||||
const { originalMessage } = await email.send({
|
||||
template: path.join(__dirname, 'templates', 'registration'),
|
||||
message: {
|
||||
to,
|
||||
},
|
||||
locals: {
|
||||
...defaultParams,
|
||||
locale,
|
||||
actionUrl,
|
||||
nonce,
|
||||
renderSettingsUrl: false,
|
||||
},
|
||||
})
|
||||
return originalMessage as OriginalMessage
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
interface EmailVerificationInput extends VerifyMailInput {
|
||||
name: string
|
||||
}
|
||||
|
||||
export const sendEmailVerification = async (
|
||||
data: EmailVerificationInput,
|
||||
): Promise<OriginalMessage> => {
|
||||
const { nonce, locale, name } = data
|
||||
const to = data.email
|
||||
const actionUrl = new URL('/settings/my-email-address/verify', CONFIG.CLIENT_URI)
|
||||
actionUrl.searchParams.set('email', to)
|
||||
actionUrl.searchParams.set('nonce', nonce)
|
||||
|
||||
try {
|
||||
const { originalMessage } = await email.send({
|
||||
template: path.join(__dirname, 'templates', 'emailVerification'),
|
||||
message: {
|
||||
to,
|
||||
},
|
||||
locals: {
|
||||
...defaultParams,
|
||||
locale,
|
||||
actionUrl,
|
||||
nonce,
|
||||
name,
|
||||
renderSettingsUrl: false,
|
||||
},
|
||||
})
|
||||
return originalMessage as OriginalMessage
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const sendResetPasswordMail = async (
|
||||
data: EmailVerificationInput,
|
||||
): Promise<OriginalMessage> => {
|
||||
const { nonce, locale, name } = data
|
||||
const to = data.email
|
||||
const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI)
|
||||
actionUrl.searchParams.set('email', to)
|
||||
actionUrl.searchParams.set('nonce', nonce)
|
||||
try {
|
||||
const { originalMessage } = await email.send({
|
||||
template: path.join(__dirname, 'templates', 'resetPassword'),
|
||||
message: {
|
||||
to,
|
||||
},
|
||||
locals: {
|
||||
...defaultParams,
|
||||
locale,
|
||||
actionUrl,
|
||||
nonce,
|
||||
name,
|
||||
renderSettingsUrl: false,
|
||||
},
|
||||
})
|
||||
return originalMessage as OriginalMessage
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const sendWrongEmail = async (data: {
|
||||
locale: string
|
||||
email: string
|
||||
}): Promise<OriginalMessage> => {
|
||||
const { locale } = data
|
||||
const to = data.email
|
||||
const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI)
|
||||
try {
|
||||
const { originalMessage } = await email.send({
|
||||
template: path.join(__dirname, 'templates', 'wrongEmail'),
|
||||
message: {
|
||||
to,
|
||||
},
|
||||
locals: {
|
||||
...defaultParams,
|
||||
locale,
|
||||
actionUrl,
|
||||
renderSettingsUrl: false,
|
||||
},
|
||||
})
|
||||
return originalMessage as OriginalMessage
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
35
backend/src/emails/sendEmailVerification.spec.ts
Normal file
35
backend/src/emails/sendEmailVerification.spec.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { sendEmailVerification } from './sendEmail'
|
||||
|
||||
describe('sendEmailVerification', () => {
|
||||
const data: {
|
||||
email: string
|
||||
nonce: string
|
||||
locale: string
|
||||
name: string
|
||||
} = {
|
||||
email: 'user@example.org',
|
||||
nonce: '123456',
|
||||
locale: 'en',
|
||||
name: 'User',
|
||||
}
|
||||
|
||||
describe('English', () => {
|
||||
beforeEach(() => {
|
||||
data.locale = 'en'
|
||||
})
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await expect(sendEmailVerification(data)).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('German', () => {
|
||||
beforeEach(() => {
|
||||
data.locale = 'de'
|
||||
})
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await expect(sendEmailVerification(data)).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
63
backend/src/emails/sendRegistrationMail.spec.ts
Normal file
63
backend/src/emails/sendRegistrationMail.spec.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { sendRegistrationMail } from './sendEmail'
|
||||
|
||||
describe('sendRegistrationMail', () => {
|
||||
const data: {
|
||||
email: string
|
||||
nonce: string
|
||||
locale: string
|
||||
inviteCode?: string
|
||||
} = {
|
||||
email: 'user@example.org',
|
||||
nonce: '123456',
|
||||
locale: 'en',
|
||||
inviteCode: 'welcome',
|
||||
}
|
||||
|
||||
describe('with invite code', () => {
|
||||
describe('English', () => {
|
||||
beforeEach(() => {
|
||||
data.locale = 'en'
|
||||
data.inviteCode = 'welcome'
|
||||
})
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await expect(sendRegistrationMail(data)).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('German', () => {
|
||||
beforeEach(() => {
|
||||
data.locale = 'de'
|
||||
data.inviteCode = 'welcome'
|
||||
})
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await expect(sendRegistrationMail(data)).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('without invite code', () => {
|
||||
describe('English', () => {
|
||||
beforeEach(() => {
|
||||
data.locale = 'en'
|
||||
delete data.inviteCode
|
||||
})
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await expect(sendRegistrationMail(data)).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('German', () => {
|
||||
beforeEach(() => {
|
||||
data.locale = 'de'
|
||||
delete data.inviteCode
|
||||
})
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await expect(sendRegistrationMail(data)).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
35
backend/src/emails/sendResetPasswordMail.spec.ts
Normal file
35
backend/src/emails/sendResetPasswordMail.spec.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { sendResetPasswordMail } from './sendEmail'
|
||||
|
||||
describe('sendResetPasswordMail', () => {
|
||||
const data: {
|
||||
email: string
|
||||
nonce: string
|
||||
locale: string
|
||||
name: string
|
||||
} = {
|
||||
email: 'user@example.org',
|
||||
nonce: '123456',
|
||||
locale: 'en',
|
||||
name: 'Jenny Rostock',
|
||||
}
|
||||
|
||||
describe('English', () => {
|
||||
beforeEach(() => {
|
||||
data.locale = 'en'
|
||||
})
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await expect(sendResetPasswordMail(data)).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('German', () => {
|
||||
beforeEach(() => {
|
||||
data.locale = 'de'
|
||||
})
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await expect(sendResetPasswordMail(data)).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
31
backend/src/emails/sendWrongEmail.spec.ts
Normal file
31
backend/src/emails/sendWrongEmail.spec.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { sendWrongEmail } from './sendEmail'
|
||||
|
||||
describe('sendWrongEmail', () => {
|
||||
const data: {
|
||||
email: string
|
||||
locale: string
|
||||
} = {
|
||||
email: 'user@example.org',
|
||||
locale: 'en',
|
||||
}
|
||||
|
||||
describe('English', () => {
|
||||
beforeEach(() => {
|
||||
data.locale = 'en'
|
||||
})
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await expect(sendWrongEmail(data)).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('German', () => {
|
||||
beforeEach(() => {
|
||||
data.locale = 'de'
|
||||
})
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await expect(sendWrongEmail(data)).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
10
backend/src/emails/templates/emailVerification/html.pug
Normal file
10
backend/src/emails/templates/emailVerification/html.pug
Normal file
@ -0,0 +1,10 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p= t('emailVerification.introduction')
|
||||
a.button(href=actionUrl)= t('buttons.verifyEmail')
|
||||
p= t('emailVerification.doNotChange')
|
||||
|
||||
p= t('emailVerification.codeHint')
|
||||
span= nonce
|
||||
@ -0,0 +1 @@
|
||||
= `${t('subjects.newEmail')} ${APPLICATION_NAME}`
|
||||
@ -3,12 +3,15 @@
|
||||
- var organizationUrl = ORGANIZATION_URL
|
||||
- var team = APPLICATION_NAME
|
||||
- var settingsUrl = settingsUrl
|
||||
- var renderSettingsUrl = renderSettingsUrl
|
||||
p= t('general.seeYou')
|
||||
a.organization(href=organizationUrl)= team
|
||||
| !
|
||||
p= t('general.yourTeam', { team })
|
||||
br
|
||||
p= t('general.settingsHint')
|
||||
a.settings(href=settingsUrl)= t('general.settingsName')
|
||||
| !
|
||||
|
||||
if renderSettingsUrl
|
||||
br
|
||||
p= t('general.settingsHint')
|
||||
a.settings(href=settingsUrl)= t('general.settingsName')
|
||||
| !
|
||||
|
||||
|
||||
@ -50,6 +50,10 @@ a.button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #17b53e;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #000000;
|
||||
|
||||
1
backend/src/emails/templates/includes/welcome.pug
Normal file
1
backend/src/emails/templates/includes/welcome.pug
Normal file
@ -0,0 +1 @@
|
||||
h2= `${t('general.welcome')} ${APPLICATION_NAME}!`
|
||||
@ -13,11 +13,15 @@ html(lang=locale)
|
||||
.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}
|
||||
style
|
||||
include includes/webflow.css
|
||||
|
||||
|
||||
- var name = name
|
||||
body
|
||||
div.container
|
||||
include includes/header.pug
|
||||
include includes/salutation.pug
|
||||
if name
|
||||
include includes/salutation.pug
|
||||
else
|
||||
include includes/welcome.pug
|
||||
|
||||
.wrapper
|
||||
block content
|
||||
|
||||
15
backend/src/emails/templates/registration/html.pug
Normal file
15
backend/src/emails/templates/registration/html.pug
Normal file
@ -0,0 +1,15 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p= t('registration.introduction')
|
||||
a.button(href=actionUrl)= t('buttons.confirmEmail')
|
||||
p= t('registration.codeHint')
|
||||
span= nonce
|
||||
p= t('registration.codeHintException')
|
||||
|
||||
p= t('registration.notYouStart')
|
||||
a(href=ORGANIZATION_LINK)= APPLICATION_NAME
|
||||
= t('registration.notYouEnd')
|
||||
|
||||
p= t('registration.ps')
|
||||
1
backend/src/emails/templates/registration/subject.pug
Normal file
1
backend/src/emails/templates/registration/subject.pug
Normal file
@ -0,0 +1 @@
|
||||
= `${t('general.welcome')} ${APPLICATION_NAME}`
|
||||
9
backend/src/emails/templates/resetPassword/html.pug
Normal file
9
backend/src/emails/templates/resetPassword/html.pug
Normal file
@ -0,0 +1,9 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p= t('resetPassword.introduction')
|
||||
a.button(href=actionUrl)= t('buttons.confirmEmail')
|
||||
p= t('resetPassword.ignore')
|
||||
p= t('resetPassword.codeHint')
|
||||
span= nonce
|
||||
1
backend/src/emails/templates/resetPassword/subject.pug
Normal file
1
backend/src/emails/templates/resetPassword/subject.pug
Normal file
@ -0,0 +1 @@
|
||||
= `${t('subjects.resetPassword')} ${APPLICATION_NAME}`
|
||||
10
backend/src/emails/templates/wrongEmail/html.pug
Normal file
10
backend/src/emails/templates/wrongEmail/html.pug
Normal file
@ -0,0 +1,10 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
.content
|
||||
p= t('wrongEmail.introduction')
|
||||
a.button(href=actionUrl)= t('buttons.tryAgain')
|
||||
|
||||
p= t('wrongEmail.ignoreStart')
|
||||
a(href=ORGANIZATION_LINK)= APPLICATION_NAME
|
||||
= t('wrongEmail.ignoreEnd')
|
||||
1
backend/src/emails/templates/wrongEmail/subject.pug
Normal file
1
backend/src/emails/templates/wrongEmail/subject.pug
Normal file
@ -0,0 +1 @@
|
||||
= `${t('subjects.wrongEmail')} ${APPLICATION_NAME}`
|
||||
@ -69,6 +69,7 @@ export default {
|
||||
)
|
||||
return result.records.map((record) => ({
|
||||
name: record.get('user').properties.name,
|
||||
locale: record.get('user').properties.locale,
|
||||
...record.get('email').properties,
|
||||
}))
|
||||
})
|
||||
|
||||
@ -71,14 +71,14 @@ describe('passwordReset', () => {
|
||||
|
||||
describe('requestPasswordReset', () => {
|
||||
const mutation = gql`
|
||||
mutation ($email: String!) {
|
||||
requestPasswordReset(email: $email)
|
||||
mutation ($email: String!, $locale: String!) {
|
||||
requestPasswordReset(email: $email, locale: $locale)
|
||||
}
|
||||
`
|
||||
|
||||
describe('with invalid email', () => {
|
||||
beforeEach(() => {
|
||||
variables = { ...variables, email: 'non-existent@example.org' }
|
||||
variables = { ...variables, email: 'non-existent@example.org', locale: 'de' }
|
||||
})
|
||||
|
||||
it('resolves anyways', async () => {
|
||||
@ -96,7 +96,7 @@ describe('passwordReset', () => {
|
||||
|
||||
describe('with a valid email', () => {
|
||||
beforeEach(() => {
|
||||
variables = { ...variables, email: 'user@example.org' }
|
||||
variables = { ...variables, email: 'user@example.org', locale: 'de' }
|
||||
})
|
||||
|
||||
it('resolves', async () => {
|
||||
|
||||
@ -50,14 +50,14 @@ afterEach(async () => {
|
||||
|
||||
describe('Signup', () => {
|
||||
const mutation = gql`
|
||||
mutation ($email: String!, $inviteCode: String) {
|
||||
Signup(email: $email, inviteCode: $inviteCode) {
|
||||
mutation ($email: String!, $locale: String!, $inviteCode: String) {
|
||||
Signup(email: $email, locale: $locale, inviteCode: $inviteCode) {
|
||||
email
|
||||
}
|
||||
}
|
||||
`
|
||||
beforeEach(() => {
|
||||
variables = { ...variables, email: 'someuser@example.org' }
|
||||
variables = { ...variables, email: 'someuser@example.org', locale: 'de' }
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
|
||||
@ -9,7 +9,11 @@ type Query {
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
Signup(email: String!, inviteCode: String = null): EmailAddress
|
||||
Signup(
|
||||
email: String!
|
||||
locale: String!
|
||||
inviteCode: String = null
|
||||
): EmailAddress
|
||||
SignupVerification(
|
||||
nonce: String!
|
||||
email: String!
|
||||
|
||||
@ -245,7 +245,7 @@ type Mutation {
|
||||
|
||||
updateOnlineStatus(status: OnlineStatus!): Boolean!
|
||||
|
||||
requestPasswordReset(email: String!): Boolean!
|
||||
requestPasswordReset(email: String!, locale: String!): Boolean!
|
||||
resetPassword(email: String!, nonce: String!, newPassword: String!): Boolean!
|
||||
changePassword(oldPassword: String!, newPassword: String!): String!
|
||||
|
||||
|
||||
@ -2,43 +2,41 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { sendMail } from '@middleware/helpers/email/sendMail'
|
||||
import {
|
||||
signupTemplate,
|
||||
resetPasswordTemplate,
|
||||
wrongAccountTemplate,
|
||||
emailVerificationTemplate,
|
||||
} from '@middleware/helpers/email/templateBuilder'
|
||||
sendRegistrationMail,
|
||||
sendEmailVerification,
|
||||
sendResetPasswordMail,
|
||||
} from '@src/emails/sendEmail'
|
||||
|
||||
const sendSignupMail = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { inviteCode } = args
|
||||
const { inviteCode, locale } = args
|
||||
const response = await resolve(root, args, context, resolveInfo)
|
||||
const { email, nonce } = response
|
||||
if (nonce) {
|
||||
// emails that already exist do not have a nonce
|
||||
if (inviteCode) {
|
||||
await sendMail(signupTemplate({ email, variables: { nonce, inviteCode } }))
|
||||
} else {
|
||||
await sendMail(signupTemplate({ email, variables: { nonce } }))
|
||||
}
|
||||
await sendRegistrationMail({ email, nonce, locale, inviteCode })
|
||||
}
|
||||
delete response.nonce
|
||||
return response
|
||||
}
|
||||
|
||||
const sendPasswordResetMail = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { email } = args
|
||||
const { email, locale } = args
|
||||
const { email: userFound, nonce, name } = await resolve(root, args, context, resolveInfo)
|
||||
const template = userFound ? resetPasswordTemplate : wrongAccountTemplate
|
||||
await sendMail(template({ email, variables: { nonce, name } }))
|
||||
if (userFound) {
|
||||
await sendResetPasswordMail({ email, nonce, name, locale })
|
||||
} else {
|
||||
// this is an antifeature allowing unauthenticated users to spam any email with wrong-email notifications
|
||||
// await sendWrongEmail({ email, locale })
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const sendEmailVerificationMail = async (resolve, root, args, context, resolveInfo) => {
|
||||
const response = await resolve(root, args, context, resolveInfo)
|
||||
const { email, nonce, name } = response
|
||||
const { email, nonce, name, locale } = response
|
||||
if (nonce) {
|
||||
await sendMail(emailVerificationTemplate({ email, variables: { nonce, name } }))
|
||||
await sendEmailVerification({ email, nonce, name, locale })
|
||||
}
|
||||
delete response.nonce
|
||||
return response
|
||||
|
||||
@ -177,8 +177,8 @@ describe('authorization', () => {
|
||||
|
||||
describe('access Signup', () => {
|
||||
const signupMutation = gql`
|
||||
mutation ($email: String!, $inviteCode: String) {
|
||||
Signup(email: $email, inviteCode: $inviteCode) {
|
||||
mutation ($email: String!, $locale: String!, $inviteCode: String) {
|
||||
Signup(email: $email, locale: $locale, inviteCode: $inviteCode) {
|
||||
email
|
||||
}
|
||||
}
|
||||
@ -189,6 +189,7 @@ describe('authorization', () => {
|
||||
variables = {
|
||||
email: 'some@email.org',
|
||||
inviteCode: 'ABCDEF',
|
||||
locale: 'de',
|
||||
}
|
||||
CONFIG.INVITE_REGISTRATION = false
|
||||
CONFIG.PUBLIC_REGISTRATION = false
|
||||
@ -231,6 +232,7 @@ describe('authorization', () => {
|
||||
variables = {
|
||||
email: 'some@email.org',
|
||||
inviteCode: 'ABCDEF',
|
||||
locale: 'de',
|
||||
}
|
||||
CONFIG.INVITE_REGISTRATION = false
|
||||
CONFIG.PUBLIC_REGISTRATION = true
|
||||
@ -269,6 +271,7 @@ describe('authorization', () => {
|
||||
variables = {
|
||||
email: 'some@email.org',
|
||||
inviteCode: 'ABCDEF',
|
||||
locale: 'de',
|
||||
}
|
||||
authenticatedUser = null
|
||||
})
|
||||
@ -288,6 +291,7 @@ describe('authorization', () => {
|
||||
variables = {
|
||||
email: 'some@email.org',
|
||||
inviteCode: 'no valid invite code',
|
||||
locale: 'de',
|
||||
}
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
@ -25,11 +25,12 @@
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"module": "commonjs" /* Specify what module code is generated. */,
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
/* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
"paths": {
|
||||
"@config/*": ["./src/config/*"],
|
||||
"@constants/*": ["./src/constants/*"],
|
||||
"@context/*": ["./src/context/*"],
|
||||
@ -66,7 +67,7 @@
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./build", /* Specify an output folder for all emitted files. */
|
||||
"outDir": "./build" /* Specify an output folder for all emitted files. */,
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
@ -88,19 +89,19 @@
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
"useUnknownInCatchVariables": false, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
"useUnknownInCatchVariables": false /* Default catch clause variables as 'unknown' instead of 'any'. */,
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
@ -115,6 +116,6 @@
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,6 +408,13 @@
|
||||
dependencies:
|
||||
eslint-visitor-keys "^3.4.3"
|
||||
|
||||
"@eslint-community/eslint-utils@^4.5.1":
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a"
|
||||
integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^3.4.3"
|
||||
|
||||
"@eslint-community/regexpp@^4.11.0":
|
||||
version "4.12.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
|
||||
@ -1841,6 +1848,11 @@ acorn@^7.1.1:
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||
|
||||
acorn@^8.14.0, acorn@^8.5.0:
|
||||
version "8.14.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
|
||||
integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
|
||||
|
||||
acorn@^8.4.1:
|
||||
version "8.9.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59"
|
||||
@ -4034,6 +4046,13 @@ eslint-compat-utils@^0.5.1:
|
||||
dependencies:
|
||||
semver "^7.5.4"
|
||||
|
||||
eslint-compat-utils@^0.6.4:
|
||||
version "0.6.5"
|
||||
resolved "https://registry.yarnpkg.com/eslint-compat-utils/-/eslint-compat-utils-0.6.5.tgz#6b06350a1c947c4514cfa64a170a6bfdbadc7ec2"
|
||||
integrity sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ==
|
||||
dependencies:
|
||||
semver "^7.5.4"
|
||||
|
||||
eslint-config-prettier@^10.1.2:
|
||||
version "10.1.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz#31a4b393c40c4180202c27e829af43323bf85276"
|
||||
@ -4065,6 +4084,13 @@ eslint-import-resolver-typescript@^4.3.4:
|
||||
tinyglobby "^0.2.13"
|
||||
unrs-resolver "^1.6.3"
|
||||
|
||||
eslint-json-compat-utils@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-json-compat-utils/-/eslint-json-compat-utils-0.2.1.tgz#32931d42c723da383712f25177a2c57b9ef5f079"
|
||||
integrity sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==
|
||||
dependencies:
|
||||
esquery "^1.6.0"
|
||||
|
||||
eslint-module-utils@^2.12.0:
|
||||
version "2.12.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz#fe4cfb948d61f49203d7b08871982b65b9af0b0b"
|
||||
@ -4113,6 +4139,20 @@ eslint-plugin-jest@^28.11.0:
|
||||
dependencies:
|
||||
"@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
|
||||
eslint-plugin-jsonc@^2.20.0:
|
||||
version "2.20.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.20.0.tgz#7f3ae51abd38176487ba7324dee77578a92e15e0"
|
||||
integrity sha512-FRgCn9Hzk5eKboCbVMrr9QrhM0eO4G+WKH8IFXoaeqhM/2kuWzbStJn4kkr0VWL8J5H8RYZF+Aoam1vlBaZVkw==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.5.1"
|
||||
eslint-compat-utils "^0.6.4"
|
||||
eslint-json-compat-utils "^0.2.1"
|
||||
espree "^9.6.1 || ^10.3.0"
|
||||
graphemer "^1.4.0"
|
||||
jsonc-eslint-parser "^2.4.0"
|
||||
natural-compare "^1.4.0"
|
||||
synckit "^0.6.2 || ^0.7.3 || ^0.10.3"
|
||||
|
||||
eslint-plugin-n@^17.17.0:
|
||||
version "17.17.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-17.17.0.tgz#6644433d395c2ecae0b2fe58018807e85d8e0724"
|
||||
@ -4170,11 +4210,16 @@ eslint-scope@^7.2.2:
|
||||
esrecurse "^4.3.0"
|
||||
estraverse "^5.2.0"
|
||||
|
||||
eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
|
||||
eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
|
||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||
|
||||
eslint-visitor-keys@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45"
|
||||
integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
|
||||
|
||||
eslint@^8.57.1:
|
||||
version "8.57.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9"
|
||||
@ -4229,7 +4274,7 @@ esniff@^2.0.1:
|
||||
event-emitter "^0.3.5"
|
||||
type "^2.7.2"
|
||||
|
||||
espree@^9.6.0, espree@^9.6.1:
|
||||
espree@^9.0.0, espree@^9.6.0, espree@^9.6.1:
|
||||
version "9.6.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
|
||||
integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
|
||||
@ -4238,6 +4283,15 @@ espree@^9.6.0, espree@^9.6.1:
|
||||
acorn-jsx "^5.3.2"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
|
||||
"espree@^9.6.1 || ^10.3.0":
|
||||
version "10.3.0"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a"
|
||||
integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==
|
||||
dependencies:
|
||||
acorn "^8.14.0"
|
||||
acorn-jsx "^5.3.2"
|
||||
eslint-visitor-keys "^4.2.0"
|
||||
|
||||
esprima@^1.2.0:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.2.5.tgz#0993502feaf668138325756f30f9a51feeec11e9"
|
||||
@ -4255,6 +4309,13 @@ esquery@^1.4.2:
|
||||
dependencies:
|
||||
estraverse "^5.1.0"
|
||||
|
||||
esquery@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
|
||||
integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
|
||||
dependencies:
|
||||
estraverse "^5.1.0"
|
||||
|
||||
esrecurse@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
|
||||
@ -6529,6 +6590,16 @@ json5@^2.2.2, json5@^2.2.3, json5@^2.x:
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
jsonc-eslint-parser@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz#74ded53f9d716e8d0671bd167bf5391f452d5461"
|
||||
integrity sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==
|
||||
dependencies:
|
||||
acorn "^8.5.0"
|
||||
eslint-visitor-keys "^3.0.0"
|
||||
espree "^9.0.0"
|
||||
semver "^7.3.5"
|
||||
|
||||
jsonwebtoken@^8.3.0, jsonwebtoken@~8.5.1:
|
||||
version "8.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
|
||||
@ -9121,7 +9192,14 @@ string_decoder@^1.3.0:
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@ -9225,6 +9303,14 @@ synckit@^0.11.0:
|
||||
"@pkgr/core" "^0.2.0"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"synckit@^0.6.2 || ^0.7.3 || ^0.10.3":
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.10.3.tgz#940aea2c7b6d141a4f74dbdebc81e0958c331a4b"
|
||||
integrity sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ==
|
||||
dependencies:
|
||||
"@pkgr/core" "^0.2.0"
|
||||
tslib "^2.8.1"
|
||||
|
||||
tapable@^2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
|
||||
@ -10087,7 +10173,16 @@ with@^7.0.0:
|
||||
assert-never "^1.2.1"
|
||||
babel-walk "3.0.0-canary-5"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0, wrap-ansi@^8.1.0:
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@7.0.0, wrap-ansi@^7.0.0, wrap-ansi@^8.1.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
||||
@ -8,6 +8,6 @@ defineStep('I should see my comment', () => {
|
||||
.get('.profile-avatar img')
|
||||
.should('have.attr', 'src')
|
||||
.and('contain', 'https://') // some url
|
||||
.get('.user-teaser > .info > .text')
|
||||
.get('.user-teaser .info > .text')
|
||||
.should('contain', 'today at')
|
||||
})
|
||||
|
||||
@ -21,4 +21,4 @@ version: 0.1.0
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "3.4.0"
|
||||
appVersion: "3.5.0"
|
||||
|
||||
@ -21,4 +21,4 @@ version: 0.1.0
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "3.4.0"
|
||||
appVersion: "3.5.0"
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ocelot-social-frontend",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ocelot-social-frontend",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^2.0.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-frontend",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"description": "ocelot.social new Frontend (in development and not fully implemented) by IT4C Boilerplate for frontends",
|
||||
"main": "build/index.js",
|
||||
"type": "module",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"description": "Free and open source software program code available to run social networks.",
|
||||
"author": "ocelot.social Community",
|
||||
"license": "MIT",
|
||||
|
||||
@ -150,7 +150,7 @@ $font-size-xx-large: 2rem;
|
||||
$font-size-x-large: 1.5rem;
|
||||
$font-size-large: 1.25rem;
|
||||
$font-size-base: 1rem;
|
||||
$font-size-body: 15px;
|
||||
$font-size-body: 0.938rem;
|
||||
$font-size-small: 0.8rem;
|
||||
$font-size-x-small: 0.7rem;
|
||||
$font-size-xx-small: 0.6rem;
|
||||
@ -359,37 +359,37 @@ $media-query-medium: (min-width: 768px);
|
||||
$media-query-large: (min-width: 1024px);
|
||||
$media-query-x-large: (min-width: 1200px);
|
||||
|
||||
/**
|
||||
/**
|
||||
* @tokens Background Images
|
||||
*/
|
||||
|
||||
/**
|
||||
/**
|
||||
* @tokens Header Color
|
||||
*/
|
||||
|
||||
$color-header-background: $color-neutral-100;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @tokens Footer Color
|
||||
*/
|
||||
|
||||
$color-footer-background: $color-neutral-100;
|
||||
$color-footer-link: $color-primary;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @tokens Locale Menu Color
|
||||
*/
|
||||
|
||||
$color-locale-menu: $text-color-soft;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @tokens Donation Bar Color
|
||||
*/
|
||||
|
||||
$color-donation-bar: $color-primary;
|
||||
$color-donation-bar-light: $color-primary-light;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @tokens Toast Color
|
||||
*/
|
||||
|
||||
|
||||
@ -52,11 +52,6 @@ $easeOut: cubic-bezier(0.19, 1, 0.22, 1);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
body.dropdown-open {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
display: block;
|
||||
padding: 15px 20px 15px 45px;
|
||||
@ -140,6 +135,10 @@ hr {
|
||||
opacity: 1;
|
||||
transition-delay: 0;
|
||||
transition: opacity 80ms ease-out;
|
||||
|
||||
@media(hover: none) {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +154,6 @@ hr {
|
||||
[class$='menu-popover'] {
|
||||
min-width: 130px;
|
||||
|
||||
a,
|
||||
button {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
@ -179,4 +177,4 @@ hr {
|
||||
|
||||
.dropdown-arrow {
|
||||
font-size: $font-size-xx-small;
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,32 +43,12 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
isPopoverOpen: {
|
||||
immediate: true,
|
||||
handler(isOpen) {
|
||||
try {
|
||||
if (isOpen) {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const paddingRightStyle = `${
|
||||
window.innerWidth - document.documentElement.clientWidth
|
||||
}px`
|
||||
const navigationElement = document.querySelector('.main-navigation')
|
||||
document.body.style.paddingRight = paddingRightStyle
|
||||
document.body.classList.add('dropdown-open')
|
||||
if (navigationElement) {
|
||||
navigationElement.style.paddingRight = paddingRightStyle
|
||||
}
|
||||
}, 20)
|
||||
})
|
||||
} else {
|
||||
const navigationElement = document.querySelector('.main-navigation')
|
||||
document.body.style.paddingRight = null
|
||||
document.body.classList.remove('dropdown-open')
|
||||
if (navigationElement) {
|
||||
navigationElement.style.paddingRight = null
|
||||
}
|
||||
}
|
||||
} catch (err) {}
|
||||
if (isOpen) {
|
||||
document.body.classList.add('dropdown-open')
|
||||
} else {
|
||||
document.body.classList.remove('dropdown-open')
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
<user-teaser
|
||||
:user="isGroup ? notification.relatedUser : from.author"
|
||||
:date-time="from.createdAt"
|
||||
:show-popover="false"
|
||||
/>
|
||||
</client-only>
|
||||
<p class="description">{{ $t(`notifications.reason.${notification.reason}`) }}</p>
|
||||
|
||||
@ -127,7 +127,7 @@ export default {
|
||||
apollo: {
|
||||
notifications: {
|
||||
query() {
|
||||
return notificationQuery(this.$i18n)
|
||||
return notificationQuery()
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
|
||||
@ -88,7 +88,7 @@ describe('NotificationsTable.vue', () => {
|
||||
})
|
||||
|
||||
it('renders the author', () => {
|
||||
const userinfo = firstRowNotification.find('.user-teaser > .info')
|
||||
const userinfo = firstRowNotification.find('.user-teaser .info')
|
||||
expect(userinfo.text()).toContain(postNotification.from.author.name)
|
||||
})
|
||||
|
||||
@ -121,7 +121,7 @@ describe('NotificationsTable.vue', () => {
|
||||
})
|
||||
|
||||
it('renders the author', () => {
|
||||
const userinfo = secondRowNotification.find('.user-teaser > .info')
|
||||
const userinfo = secondRowNotification.find('.user-teaser .info')
|
||||
expect(userinfo.text()).toContain(commentNotification.from.author.name)
|
||||
})
|
||||
|
||||
|
||||
@ -59,7 +59,12 @@ describe('Request', () => {
|
||||
})
|
||||
|
||||
it('delivers email to backend', () => {
|
||||
const expected = expect.objectContaining({ variables: { email: 'mail@example.org' } })
|
||||
const expected = expect.objectContaining({
|
||||
variables: {
|
||||
email: 'mail@example.org',
|
||||
locale: 'en',
|
||||
},
|
||||
})
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
@ -92,7 +97,12 @@ describe('Request', () => {
|
||||
})
|
||||
|
||||
it('normalizes email to lower case letters', () => {
|
||||
const expected = expect.objectContaining({ variables: { email: 'mail@gmail.com' } })
|
||||
const expected = expect.objectContaining({
|
||||
variables: {
|
||||
email: 'mail@gmail.com',
|
||||
locale: 'en',
|
||||
},
|
||||
})
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
})
|
||||
|
||||
@ -85,13 +85,13 @@ export default {
|
||||
},
|
||||
async handleSubmit() {
|
||||
const mutation = gql`
|
||||
mutation ($email: String!) {
|
||||
requestPasswordReset(email: $email)
|
||||
mutation ($email: String!, $locale: String!) {
|
||||
requestPasswordReset(email: $email, locale: $locale)
|
||||
}
|
||||
`
|
||||
try {
|
||||
const { email } = this
|
||||
await this.$apollo.mutate({ mutation, variables: { email } })
|
||||
await this.$apollo.mutate({ mutation, variables: { email, locale: this.$i18n.locale() } })
|
||||
this.submitted = true
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@ -36,8 +36,8 @@ import normalizeEmail from '~/components/utils/NormalizeEmail'
|
||||
import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
|
||||
|
||||
export const SignupMutation = gql`
|
||||
mutation ($email: String!, $inviteCode: String) {
|
||||
Signup(email: $email, inviteCode: $inviteCode) {
|
||||
mutation ($email: String!, $locale: String!, $inviteCode: String) {
|
||||
Signup(email: $email, locale: $locale, inviteCode: $inviteCode) {
|
||||
email
|
||||
}
|
||||
}
|
||||
@ -140,7 +140,7 @@ export default {
|
||||
async onNextClick() {
|
||||
const { email } = this.formData
|
||||
const { inviteCode = null } = this.sliderData.collectedInputData
|
||||
const variables = { email, inviteCode }
|
||||
const variables = { email, inviteCode, locale: this.$i18n.locale() }
|
||||
|
||||
if (this.sliderData.collectedInputData.emailSend && !this.sendEmailAgain) {
|
||||
return true
|
||||
|
||||
@ -25,6 +25,9 @@ describe('Signup', () => {
|
||||
loading: false,
|
||||
mutate: jest.fn().mockResolvedValue({ data: { Signup: { email: 'mail@example.org' } } }),
|
||||
},
|
||||
$i18n: {
|
||||
locale: () => 'de',
|
||||
},
|
||||
}
|
||||
propsData = {}
|
||||
})
|
||||
@ -64,7 +67,7 @@ describe('Signup', () => {
|
||||
it('delivers email to backend', () => {
|
||||
const expected = expect.objectContaining({
|
||||
mutation: SignupMutation,
|
||||
variables: { email: 'mAIL@exAMPLE.org', inviteCode: null },
|
||||
variables: { email: 'mAIL@exAMPLE.org', locale: 'de', inviteCode: null },
|
||||
})
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
@ -70,8 +70,8 @@ import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
|
||||
|
||||
export const SignupMutation = gql`
|
||||
mutation ($email: String!, $inviteCode: String) {
|
||||
Signup(email: $email, inviteCode: $inviteCode) {
|
||||
mutation ($email: String!, $locale: String!, $inviteCode: String) {
|
||||
Signup(email: $email, locale: $locale, inviteCode: $inviteCode) {
|
||||
email
|
||||
}
|
||||
}
|
||||
@ -121,7 +121,7 @@ export default {
|
||||
try {
|
||||
const response = await this.$apollo.mutate({
|
||||
mutation: SignupMutation,
|
||||
variables: { email, inviteCode: null },
|
||||
variables: { email, locale: this.$i18n.locale(), inviteCode: null },
|
||||
})
|
||||
this.data = response.data
|
||||
setTimeout(() => {
|
||||
|
||||
31
webapp/components/UserTeaser/LocationInfo.spec.js
Normal file
31
webapp/components/UserTeaser/LocationInfo.spec.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { render } from '@testing-library/vue'
|
||||
import LocationInfo from './LocationInfo.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('LocationInfo', () => {
|
||||
const Wrapper = ({ withDistance }) => {
|
||||
return render(LocationInfo, {
|
||||
localVue,
|
||||
propsData: {
|
||||
locationData: {
|
||||
name: 'Paris',
|
||||
distanceToMe: withDistance ? 100 : null,
|
||||
},
|
||||
},
|
||||
mocks: {
|
||||
$t: jest.fn((t) => t),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
it('renders with distance', () => {
|
||||
const wrapper = Wrapper({ withDistance: true })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders without distance', () => {
|
||||
const wrapper = Wrapper({ withDistance: false })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
44
webapp/components/UserTeaser/LocationInfo.vue
Normal file
44
webapp/components/UserTeaser/LocationInfo.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="location-info">
|
||||
<div class="location">
|
||||
<base-icon name="map-marker" />
|
||||
{{ locationData.name }}
|
||||
</div>
|
||||
<div v-if="distance" class="distance">{{ distance }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LocationInfo',
|
||||
props: {
|
||||
locationData: { type: Object, default: null },
|
||||
},
|
||||
computed: {
|
||||
distance() {
|
||||
return this.locationData.distanceToMe === null
|
||||
? null
|
||||
: this.$t('location.distance', { distance: this.locationData.distanceToMe })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.location-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.location {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.distance {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,113 +1,250 @@
|
||||
import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||
import { render, screen, fireEvent } from '@testing-library/vue'
|
||||
import { RouterLinkStub } from '@vue/test-utils'
|
||||
import UserTeaser from './UserTeaser.vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
const localVue = global.localVue
|
||||
const filter = jest.fn((str) => str)
|
||||
|
||||
localVue.filter('truncate', filter)
|
||||
// Mock Math.random, used in Dropdown
|
||||
Object.assign(Math, {
|
||||
random: () => 0,
|
||||
})
|
||||
|
||||
const waitForPopover = async () => await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
let mockIsTouchDevice
|
||||
jest.mock('../utils/isTouchDevice', () => ({
|
||||
isTouchDevice: jest.fn(() => mockIsTouchDevice),
|
||||
}))
|
||||
|
||||
const userTilda = {
|
||||
name: 'Tilda Swinton',
|
||||
slug: 'tilda-swinton',
|
||||
id: 'user1',
|
||||
avatar: '/avatars/tilda-swinton',
|
||||
badgeVerification: {
|
||||
id: 'bv1',
|
||||
icon: '/icons/verified',
|
||||
description: 'Verified',
|
||||
isDefault: false,
|
||||
},
|
||||
badgeTrophiesSelected: [
|
||||
{
|
||||
id: 'trophy1',
|
||||
icon: '/icons/trophy1',
|
||||
description: 'Trophy 1',
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
id: 'trophy2',
|
||||
icon: '/icons/trophy2',
|
||||
description: 'Trophy 2',
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
id: 'empty',
|
||||
icon: '/icons/empty',
|
||||
description: 'Empty',
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
describe('UserTeaser', () => {
|
||||
let propsData
|
||||
let mocks
|
||||
let stubs
|
||||
let getters
|
||||
const Wrapper = ({
|
||||
isModerator = false,
|
||||
withLinkToProfile = true,
|
||||
onTouchScreen = false,
|
||||
withAvatar = true,
|
||||
user = userTilda,
|
||||
withPopoverEnabled = true,
|
||||
}) => {
|
||||
mockIsTouchDevice = onTouchScreen
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {}
|
||||
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
}
|
||||
stubs = {
|
||||
NuxtLink: RouterLinkStub,
|
||||
}
|
||||
getters = {
|
||||
'auth/user': () => {
|
||||
return {}
|
||||
const store = new Vuex.Store({
|
||||
getters: {
|
||||
'auth/user': () => {
|
||||
return {}
|
||||
},
|
||||
'auth/isModerator': () => isModerator,
|
||||
},
|
||||
'auth/isModerator': () => false,
|
||||
}
|
||||
})
|
||||
return render(UserTeaser, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: {
|
||||
user,
|
||||
linkToProfile: withLinkToProfile,
|
||||
showAvatar: withAvatar,
|
||||
showPopover: withPopoverEnabled,
|
||||
},
|
||||
stubs: {
|
||||
NuxtLink: RouterLinkStub,
|
||||
'user-teaser-popover': true,
|
||||
'v-popover': true,
|
||||
'client-only': true,
|
||||
},
|
||||
mocks: {
|
||||
$t: jest.fn((t) => t),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
it('renders anonymous user', () => {
|
||||
const wrapper = Wrapper({ user: null })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
describe('given an user', () => {
|
||||
describe('without linkToProfile, on touch screen', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper({ withLinkToProfile: false, onTouchScreen: true })
|
||||
})
|
||||
return mount(UserTeaser, { store, propsData, mocks, stubs, localVue })
|
||||
}
|
||||
|
||||
it('renders anonymous user', () => {
|
||||
const wrapper = Wrapper()
|
||||
expect(wrapper.text()).toBe('')
|
||||
expect(mocks.$t).toHaveBeenCalledWith('profile.userAnonym')
|
||||
it('renders', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe('when clicking the user name', () => {
|
||||
beforeEach(async () => {
|
||||
const userName = screen.getByText('Tilda Swinton')
|
||||
await fireEvent.click(userName)
|
||||
await waitForPopover()
|
||||
})
|
||||
|
||||
it('renders the popover', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when clicking the user avatar', () => {
|
||||
beforeEach(async () => {
|
||||
const userAvatar = screen.getByAltText('Tilda Swinton')
|
||||
await fireEvent.click(userAvatar)
|
||||
await waitForPopover()
|
||||
})
|
||||
|
||||
it('renders the popover', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('given an user', () => {
|
||||
describe('with linkToProfile, on touch screen', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
propsData.user = {
|
||||
name: 'Tilda Swinton',
|
||||
slug: 'tilda-swinton',
|
||||
}
|
||||
wrapper = Wrapper({ withLinkToProfile: true, onTouchScreen: true })
|
||||
})
|
||||
|
||||
it('renders user name', () => {
|
||||
const wrapper = Wrapper()
|
||||
expect(mocks.$t).not.toHaveBeenCalledWith('profile.userAnonym')
|
||||
expect(wrapper.text()).toMatch('Tilda Swinton')
|
||||
it('renders', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe('user is deleted', () => {
|
||||
beforeEach(() => {
|
||||
propsData.user.deleted = true
|
||||
describe('when clicking the user name', () => {
|
||||
beforeEach(async () => {
|
||||
const userName = screen.getByText('Tilda Swinton')
|
||||
await fireEvent.click(userName)
|
||||
})
|
||||
|
||||
it('renders the popover', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('without linkToProfile, on desktop', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper({ withLinkToProfile: false, onTouchScreen: false })
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe('when hovering the user name', () => {
|
||||
beforeEach(async () => {
|
||||
const userName = screen.getByText('Tilda Swinton')
|
||||
await fireEvent.mouseOver(userName)
|
||||
await waitForPopover()
|
||||
})
|
||||
|
||||
it('renders the popover', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when hovering the user avatar', () => {
|
||||
beforeEach(async () => {
|
||||
const userAvatar = screen.getByAltText('Tilda Swinton')
|
||||
await fireEvent.mouseOver(userAvatar)
|
||||
await waitForPopover()
|
||||
})
|
||||
|
||||
it('renders the popover', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with linkToProfile, on desktop', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper({ withLinkToProfile: true, onTouchScreen: false })
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe('when hovering the user name', () => {
|
||||
beforeEach(async () => {
|
||||
const userName = screen.getByText('Tilda Swinton')
|
||||
await fireEvent.mouseOver(userName)
|
||||
await waitForPopover()
|
||||
})
|
||||
|
||||
it('renders the popover', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('avatar is disabled', () => {
|
||||
it('does not render the avatar', () => {
|
||||
const wrapper = Wrapper({ withAvatar: false })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('user is deleted', () => {
|
||||
it('renders anonymous user', () => {
|
||||
const wrapper = Wrapper({ user: { ...userTilda, deleted: true } })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe('even if the current user is a moderator', () => {
|
||||
it('renders anonymous user', () => {
|
||||
const wrapper = Wrapper()
|
||||
expect(wrapper.text()).not.toMatch('Tilda Swinton')
|
||||
expect(mocks.$t).toHaveBeenCalledWith('profile.userAnonym')
|
||||
})
|
||||
|
||||
describe('even if the current user is a moderator', () => {
|
||||
beforeEach(() => {
|
||||
getters['auth/isModerator'] = () => true
|
||||
})
|
||||
|
||||
it('renders anonymous user', () => {
|
||||
const wrapper = Wrapper()
|
||||
expect(wrapper.text()).not.toMatch('Tilda Swinton')
|
||||
expect(mocks.$t).toHaveBeenCalledWith('profile.userAnonym')
|
||||
const wrapper = Wrapper({
|
||||
user: { ...userTilda, deleted: true },
|
||||
isModerator: true,
|
||||
})
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('user is disabled', () => {
|
||||
beforeEach(() => {
|
||||
propsData.user.disabled = true
|
||||
})
|
||||
describe('user is disabled', () => {
|
||||
it('renders anonymous user', () => {
|
||||
const wrapper = Wrapper({ user: { ...userTilda, disabled: true } })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders anonymous user', () => {
|
||||
const wrapper = Wrapper()
|
||||
expect(wrapper.text()).not.toMatch('Tilda Swinton')
|
||||
expect(mocks.$t).toHaveBeenCalledWith('profile.userAnonym')
|
||||
})
|
||||
|
||||
describe('current user is a moderator', () => {
|
||||
beforeEach(() => {
|
||||
getters['auth/isModerator'] = () => true
|
||||
})
|
||||
|
||||
it('renders user name', () => {
|
||||
const wrapper = Wrapper()
|
||||
expect(wrapper.text()).not.toMatch('Anonymous')
|
||||
expect(wrapper.text()).toMatch('Tilda Swinton')
|
||||
})
|
||||
|
||||
it('has "disabled-content" class', () => {
|
||||
const wrapper = Wrapper()
|
||||
expect(wrapper.classes()).toContain('disabled-content')
|
||||
})
|
||||
describe('current user is a moderator', () => {
|
||||
it('renders user name', () => {
|
||||
const wrapper = Wrapper({ user: { ...userTilda, disabled: true }, isModerator: true })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,57 +4,34 @@
|
||||
<span class="info anonymous">{{ $t('profile.userAnonym') }}</span>
|
||||
</div>
|
||||
<div v-else :class="[{ 'disabled-content': user.disabled }]" placement="top-start">
|
||||
<div :class="['user-teaser']">
|
||||
<nuxt-link v-if="linkToProfile && showAvatar" :to="userLink" data-test="avatarUserLink">
|
||||
<profile-avatar :profile="user" size="small" />
|
||||
</nuxt-link>
|
||||
<profile-avatar v-else-if="showAvatar" :profile="user" size="small" />
|
||||
<div class="info flex-direction-column">
|
||||
<div :class="wide ? 'flex-direction-row' : 'flex-direction-column'">
|
||||
<nuxt-link v-if="linkToProfile" :to="userLink">
|
||||
<span class="text">
|
||||
<span class="slug">{{ userSlug }}</span>
|
||||
<span class="name">{{ userName }}</span>
|
||||
</span>
|
||||
</nuxt-link>
|
||||
<span v-else class="text">
|
||||
<span class="slug">{{ userSlug }}</span>
|
||||
<span class="name">{{ userName }}</span>
|
||||
</span>
|
||||
<span v-if="wide"> </span>
|
||||
<span v-if="group">
|
||||
<span class="text">
|
||||
{{ $t('group.in') }}
|
||||
</span>
|
||||
<nuxt-link :to="groupLink">
|
||||
<span class="text">
|
||||
<span class="slug">{{ groupSlug }}</span>
|
||||
<span v-if="!userOnly" class="name">{{ groupName }}</span>
|
||||
</span>
|
||||
</nuxt-link>
|
||||
</span>
|
||||
</div>
|
||||
<span v-if="!userOnly && dateTime" class="text">
|
||||
<base-icon name="clock" />
|
||||
<date-time :date-time="dateTime" />
|
||||
<slot name="dateTime"></slot>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- isTouchDevice only supported on client-->
|
||||
<client-only>
|
||||
<user-teaser-non-anonymous
|
||||
v-if="user"
|
||||
:link-to-profile="linkToProfile"
|
||||
:user="user"
|
||||
:group="group"
|
||||
:wide="wide"
|
||||
:show-avatar="showAvatar"
|
||||
:date-time="dateTime"
|
||||
:show-popover="showPopover"
|
||||
@close="closeMenu"
|
||||
/>
|
||||
</client-only>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import DateTime from '~/components/DateTime'
|
||||
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
|
||||
import UserTeaserNonAnonymous from './UserTeaserNonAnonymous'
|
||||
|
||||
export default {
|
||||
name: 'UserTeaser',
|
||||
components: {
|
||||
DateTime,
|
||||
ProfileAvatar,
|
||||
UserTeaserNonAnonymous,
|
||||
},
|
||||
props: {
|
||||
linkToProfile: { type: Boolean, default: true },
|
||||
@ -69,71 +46,36 @@ export default {
|
||||
...mapGetters({
|
||||
isModerator: 'auth/isModerator',
|
||||
}),
|
||||
itsMe() {
|
||||
return this.user.slug === this.$store.getters['auth/user'].slug
|
||||
},
|
||||
displayAnonymous() {
|
||||
const { user, isModerator } = this
|
||||
return !user || user.deleted || (user.disabled && !isModerator)
|
||||
},
|
||||
userLink() {
|
||||
const { id, slug } = this.user
|
||||
if (!(id && slug)) return ''
|
||||
return { name: 'profile-id-slug', params: { slug, id } }
|
||||
},
|
||||
userSlug() {
|
||||
const { slug } = this.user || {}
|
||||
return slug && `@${slug}`
|
||||
},
|
||||
userName() {
|
||||
const { name } = this.user || {}
|
||||
return name || this.$t('profile.userAnonym')
|
||||
},
|
||||
userOnly() {
|
||||
return !this.dateTime && !this.group
|
||||
},
|
||||
groupLink() {
|
||||
const { id, slug } = this.group
|
||||
if (!(id && slug)) return ''
|
||||
return { name: 'groups-id-slug', params: { slug, id } }
|
||||
},
|
||||
groupSlug() {
|
||||
const { slug } = this.group || {}
|
||||
return slug && `&${slug}`
|
||||
},
|
||||
groupName() {
|
||||
const { name } = this.group || {}
|
||||
return name || this.$t('profile.userAnonym')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
optimisticFollow({ followedByCurrentUser }) {
|
||||
const inc = followedByCurrentUser ? 1 : -1
|
||||
this.user.followedByCurrentUser = followedByCurrentUser
|
||||
this.user.followedByCount += inc
|
||||
},
|
||||
updateFollow({ followedByCurrentUser, followedByCount }) {
|
||||
this.user.followedByCount = followedByCount
|
||||
this.user.followedByCurrentUser = followedByCurrentUser
|
||||
closeMenu() {
|
||||
this.$emit('close')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.trigger {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.user-teaser {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
> .profile-avatar {
|
||||
.trigger {
|
||||
max-width: 100%;
|
||||
display: flex !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
> .info {
|
||||
.info {
|
||||
padding-left: $space-xx-small;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -145,12 +87,12 @@ export default {
|
||||
|
||||
.slug {
|
||||
color: $color-primary;
|
||||
font-size: $font-size-base;
|
||||
font-size: calc(1.15 * $font-size-base);
|
||||
}
|
||||
|
||||
.name {
|
||||
color: $text-color-soft;
|
||||
font-size: $font-size-small;
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,12 +100,14 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.flex-direction-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.text {
|
||||
|
||||
71
webapp/components/UserTeaser/UserTeaserHelper.spec.js
Normal file
71
webapp/components/UserTeaser/UserTeaserHelper.spec.js
Normal file
@ -0,0 +1,71 @@
|
||||
import { render } from '@testing-library/vue'
|
||||
import { RouterLinkStub } from '@vue/test-utils'
|
||||
import UserTeaserHelper from './UserTeaserHelper.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const userLink = {
|
||||
name: 'profile-id-slug',
|
||||
params: { slug: 'slug', id: 'id' },
|
||||
}
|
||||
|
||||
let mockIsTouchDevice
|
||||
|
||||
jest.mock('../utils/isTouchDevice', () => ({
|
||||
isTouchDevice: jest.fn(() => mockIsTouchDevice),
|
||||
}))
|
||||
|
||||
describe('UserTeaserHelper', () => {
|
||||
const Wrapper = ({
|
||||
withLinkToProfile = true,
|
||||
onTouchScreen = false,
|
||||
withPopoverEnabled = true,
|
||||
}) => {
|
||||
mockIsTouchDevice = onTouchScreen
|
||||
|
||||
return render(UserTeaserHelper, {
|
||||
localVue,
|
||||
propsData: {
|
||||
userLink,
|
||||
linkToProfile: withLinkToProfile,
|
||||
showPopover: withPopoverEnabled,
|
||||
},
|
||||
stubs: {
|
||||
NuxtLink: RouterLinkStub,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('with linkToProfile and popover enabled, on touch screen', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper({ withLinkToProfile: true, onTouchScreen: true, withPopoverEnabled: true })
|
||||
})
|
||||
|
||||
it('renders button', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('without linkToProfile', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper({ withLinkToProfile: false, onTouchScreen: false })
|
||||
})
|
||||
|
||||
it('renders span', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('with linkToProfile, on desktop', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper({ withLinkToProfile: true, onTouchScreen: false })
|
||||
})
|
||||
|
||||
it('renders link', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
46
webapp/components/UserTeaser/UserTeaserHelper.vue
Normal file
46
webapp/components/UserTeaser/UserTeaserHelper.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<button v-if="showPopover && isTouchDevice" @click.prevent="openMenu">
|
||||
<slot />
|
||||
</button>
|
||||
<span
|
||||
v-else-if="!linkToProfile || !userLink"
|
||||
@mouseover="() => showPopover && openMenu()"
|
||||
@mouseleave="closeMenu"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
<nuxt-link
|
||||
v-else
|
||||
:to="userLink"
|
||||
@mouseover.native="() => showPopover && openMenu()"
|
||||
@mouseleave.native="closeMenu"
|
||||
>
|
||||
<slot />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isTouchDevice } from '../utils/isTouchDevice'
|
||||
|
||||
export default {
|
||||
name: 'UserTeaserHelper',
|
||||
props: {
|
||||
userLink: { type: Object, default: null },
|
||||
linkToProfile: { type: Boolean, default: true },
|
||||
showPopover: { type: Boolean, default: false },
|
||||
},
|
||||
computed: {
|
||||
isTouchDevice() {
|
||||
return isTouchDevice()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openMenu() {
|
||||
this.$emit('open-menu')
|
||||
},
|
||||
closeMenu() {
|
||||
this.$emit('close-menu')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
123
webapp/components/UserTeaser/UserTeaserNonAnonymous.vue
Normal file
123
webapp/components/UserTeaser/UserTeaserNonAnonymous.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<dropdown class="user-teaser">
|
||||
<template #default="{ openMenu, closeMenu }">
|
||||
<user-teaser-helper
|
||||
v-if="showAvatar"
|
||||
:link-to-profile="linkToProfile"
|
||||
:show-popover="showPopover"
|
||||
:user-link="userLink"
|
||||
@open-menu="openMenu(false)"
|
||||
@close-menu="closeMenu(false)"
|
||||
data-test="avatarUserLink"
|
||||
>
|
||||
<profile-avatar :profile="user" size="small" />
|
||||
</user-teaser-helper>
|
||||
<div class="info flex-direction-column">
|
||||
<div :class="wide ? 'flex-direction-row' : 'flex-direction-column'">
|
||||
<user-teaser-helper
|
||||
:link-to-profile="linkToProfile"
|
||||
:show-popover="showPopover"
|
||||
:user-link="userLink"
|
||||
@open-menu="openMenu(false)"
|
||||
@close-menu="closeMenu(false)"
|
||||
>
|
||||
<span class="slug">{{ userSlug }}</span>
|
||||
<span class="name">{{ userName }}</span>
|
||||
</user-teaser-helper>
|
||||
<span v-if="wide"> </span>
|
||||
<span v-if="group">
|
||||
<span class="text">
|
||||
{{ $t('group.in') }}
|
||||
</span>
|
||||
<nuxt-link :to="groupLink">
|
||||
<span class="text">
|
||||
<span class="slug">{{ groupSlug }}</span>
|
||||
<span v-if="!userOnly" class="name">{{ groupName }}</span>
|
||||
</span>
|
||||
</nuxt-link>
|
||||
</span>
|
||||
</div>
|
||||
<span v-if="!userOnly && dateTime" class="text">
|
||||
<base-icon name="clock" />
|
||||
<date-time :date-time="dateTime" />
|
||||
<slot name="dateTime"></slot>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #popover="{ isOpen }" v-if="showPopover">
|
||||
<user-teaser-popover
|
||||
v-if="isOpen"
|
||||
:user-id="user.id"
|
||||
:user-link="linkToProfile ? userLink : null"
|
||||
/>
|
||||
</template>
|
||||
</dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import DateTime from '~/components/DateTime'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
|
||||
import UserTeaserPopover from './UserTeaserPopover'
|
||||
import UserTeaserHelper from './UserTeaserHelper.vue'
|
||||
|
||||
export default {
|
||||
name: 'UserTeaser',
|
||||
components: {
|
||||
ProfileAvatar,
|
||||
UserTeaserPopover,
|
||||
UserTeaserHelper,
|
||||
Dropdown,
|
||||
DateTime,
|
||||
},
|
||||
props: {
|
||||
linkToProfile: { type: Boolean, default: true },
|
||||
user: { type: Object, default: null },
|
||||
group: { type: Object, default: null },
|
||||
wide: { type: Boolean, default: false },
|
||||
showAvatar: { type: Boolean, default: true },
|
||||
dateTime: { type: [Date, String], default: null },
|
||||
showPopover: { type: Boolean, default: true },
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
isModerator: 'auth/isModerator',
|
||||
}),
|
||||
|
||||
itsMe() {
|
||||
return this.user.slug === this.$store.getters['auth/user'].slug
|
||||
},
|
||||
userLink() {
|
||||
const { id, slug } = this.user
|
||||
if (!(id && slug)) return null
|
||||
return { name: 'profile-id-slug', params: { slug, id } }
|
||||
},
|
||||
userSlug() {
|
||||
const { slug } = this.user || {}
|
||||
return slug && `@${slug}`
|
||||
},
|
||||
userName() {
|
||||
const { name } = this.user || {}
|
||||
return name || this.$t('profile.userAnonym')
|
||||
},
|
||||
userOnly() {
|
||||
return !this.dateTime && !this.group
|
||||
},
|
||||
groupLink() {
|
||||
const { id, slug } = this.group
|
||||
if (!(id && slug)) return ''
|
||||
return { name: 'groups-id-slug', params: { slug, id } }
|
||||
},
|
||||
groupSlug() {
|
||||
const { slug } = this.group || {}
|
||||
return slug && `&${slug}`
|
||||
},
|
||||
groupName() {
|
||||
const { name } = this.group || {}
|
||||
return name || this.$t('profile.userAnonym')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
99
webapp/components/UserTeaser/UserTeaserPopover.spec.js
Normal file
99
webapp/components/UserTeaser/UserTeaserPopover.spec.js
Normal file
@ -0,0 +1,99 @@
|
||||
import { render } from '@testing-library/vue'
|
||||
import { RouterLinkStub } from '@vue/test-utils'
|
||||
import UserTeaserPopover from './UserTeaserPopover.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const user = {
|
||||
id: 'id',
|
||||
name: 'Tilda Swinton',
|
||||
slug: 'tilda-swinton',
|
||||
badgeVerification: {
|
||||
id: 'bv1',
|
||||
icon: '/icons/verified',
|
||||
description: 'Verified',
|
||||
isDefault: false,
|
||||
},
|
||||
badgeTrophiesSelected: [
|
||||
{
|
||||
id: 'trophy1',
|
||||
icon: '/icons/trophy1',
|
||||
description: 'Trophy 1',
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
id: 'trophy2',
|
||||
icon: '/icons/trophy2',
|
||||
description: 'Trophy 2',
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
id: 'empty',
|
||||
icon: '/icons/empty',
|
||||
description: 'Empty',
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const userLink = {
|
||||
name: 'profile-id-slug',
|
||||
params: { slug: 'slug', id: 'id' },
|
||||
}
|
||||
|
||||
describe('UserTeaserPopover', () => {
|
||||
const Wrapper = ({ badgesEnabled = true, withUserLink = true, onTouchScreen = false }) => {
|
||||
const mockIsTouchDevice = onTouchScreen
|
||||
jest.mock('../utils/isTouchDevice', () => ({
|
||||
isTouchDevice: jest.fn(() => mockIsTouchDevice),
|
||||
}))
|
||||
return render(UserTeaserPopover, {
|
||||
localVue,
|
||||
propsData: {
|
||||
userId: 'id',
|
||||
userLink: withUserLink ? userLink : null,
|
||||
},
|
||||
data: () => ({
|
||||
User: [user],
|
||||
}),
|
||||
stubs: {
|
||||
NuxtLink: RouterLinkStub,
|
||||
},
|
||||
mocks: {
|
||||
$t: jest.fn((t) => t),
|
||||
$env: {
|
||||
BADGES_ENABLED: badgesEnabled,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('given a touch device', () => {
|
||||
it('shows button when userLink is provided', () => {
|
||||
const wrapper = Wrapper({ withUserLink: true, onTouchScreen: true })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('does not show button when userLink is not provided', () => {
|
||||
const wrapper = Wrapper({ withUserLink: false, onTouchScreen: true })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('given a non-touch device', () => {
|
||||
it('does not show button when userLink is provided', () => {
|
||||
const wrapper = Wrapper({ withUserLink: true, onTouchScreen: false })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows badges when enabled', () => {
|
||||
const wrapper = Wrapper({ badgesEnabled: true })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('does not show badges when disabled', () => {
|
||||
const wrapper = Wrapper({ badgesEnabled: false })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
103
webapp/components/UserTeaser/UserTeaserPopover.vue
Normal file
103
webapp/components/UserTeaser/UserTeaserPopover.vue
Normal file
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="placeholder" v-if="!user" />
|
||||
<div class="user-teaser-popover" v-else>
|
||||
<badges
|
||||
v-if="$env.BADGES_ENABLED && user.badgeVerification"
|
||||
:badges="[user.badgeVerification, ...user.badgeTrophiesSelected]"
|
||||
/>
|
||||
<location-info v-if="user.location" :location-data="user.location" class="location-info" />
|
||||
<ul class="statistics">
|
||||
<li>
|
||||
<ds-number :count="user.followedByCount" :label="$t('profile.followers')" />
|
||||
</li>
|
||||
<li>
|
||||
<ds-number
|
||||
:count="user.contributionsCount"
|
||||
:label="$t('common.post', null, user.contributionsCount)"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<ds-number
|
||||
:count="user.commentedCount"
|
||||
:label="$t('common.comment', null, user.commentedCount)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<nuxt-link v-if="isTouchDevice && userLink" :to="userLink" class="link">
|
||||
<ds-button primary>{{ $t('user-teaser.popover.open-profile') }}</ds-button>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Badges from '~/components/Badges.vue'
|
||||
import LocationInfo from '~/components/UserTeaser/LocationInfo.vue'
|
||||
import { isTouchDevice } from '~/components/utils/isTouchDevice'
|
||||
import { userTeaserQuery } from '~/graphql/User.js'
|
||||
|
||||
export default {
|
||||
name: 'UserTeaserPopover',
|
||||
components: {
|
||||
Badges,
|
||||
LocationInfo,
|
||||
},
|
||||
props: {
|
||||
userId: { type: String },
|
||||
userLink: { type: Object },
|
||||
},
|
||||
computed: {
|
||||
isTouchDevice() {
|
||||
return isTouchDevice()
|
||||
},
|
||||
user() {
|
||||
return (this.User && this.User[0]) ?? null
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
User: {
|
||||
query() {
|
||||
return userTeaserQuery(this.$i18n)
|
||||
},
|
||||
variables() {
|
||||
return { id: this.userId }
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.placeholder {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
.user-teaser-popover {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
@media (max-height: 800px) {
|
||||
.user-teaser-popover {
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.location-info {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.link {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.statistics {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,51 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LocationInfo renders with distance 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="location-info"
|
||||
>
|
||||
<div
|
||||
class="location"
|
||||
>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
Paris
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="distance"
|
||||
>
|
||||
location.distance
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`LocationInfo renders without distance 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="location-info"
|
||||
>
|
||||
<div
|
||||
class="location"
|
||||
>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
Paris
|
||||
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
1136
webapp/components/UserTeaser/__snapshots__/UserTeaser.spec.js.snap
Normal file
1136
webapp/components/UserTeaser/__snapshots__/UserTeaser.spec.js.snap
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UserTeaserHelper with linkToProfile and popover enabled, on touch screen renders button 1`] = `
|
||||
<div>
|
||||
<button />
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserTeaserHelper with linkToProfile, on desktop renders link 1`] = `
|
||||
<div>
|
||||
<a />
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserTeaserHelper without linkToProfile renders span 1`] = `
|
||||
<div>
|
||||
<span />
|
||||
</div>
|
||||
`;
|
||||
@ -0,0 +1,547 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UserTeaserPopover does not show badges when disabled 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="user-teaser-popover"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<!---->
|
||||
|
||||
<ul
|
||||
class="statistics"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
profile.followers
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
common.post
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
common.comment
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserTeaserPopover given a non-touch device does not show button when userLink is provided 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="user-teaser-popover"
|
||||
>
|
||||
<div
|
||||
class="hc-badges"
|
||||
>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/verified"
|
||||
title="Verified"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/trophy1"
|
||||
title="Trophy 1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/trophy2"
|
||||
title="Trophy 2"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/empty"
|
||||
title="Empty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
|
||||
<ul
|
||||
class="statistics"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
profile.followers
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
common.post
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
common.comment
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserTeaserPopover given a touch device does not show button when userLink is not provided 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="user-teaser-popover"
|
||||
>
|
||||
<div
|
||||
class="hc-badges"
|
||||
>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/verified"
|
||||
title="Verified"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/trophy1"
|
||||
title="Trophy 1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/trophy2"
|
||||
title="Trophy 2"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/empty"
|
||||
title="Empty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
|
||||
<ul
|
||||
class="statistics"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
profile.followers
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
common.post
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
common.comment
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserTeaserPopover given a touch device shows button when userLink is provided 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="user-teaser-popover"
|
||||
>
|
||||
<div
|
||||
class="hc-badges"
|
||||
>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/verified"
|
||||
title="Verified"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/trophy1"
|
||||
title="Trophy 1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/trophy2"
|
||||
title="Trophy 2"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/empty"
|
||||
title="Empty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
|
||||
<ul
|
||||
class="statistics"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
profile.followers
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
common.post
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
common.comment
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserTeaserPopover shows badges when enabled 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="user-teaser-popover"
|
||||
>
|
||||
<div
|
||||
class="hc-badges"
|
||||
>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/verified"
|
||||
title="Verified"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/trophy1"
|
||||
title="Trophy 1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/trophy2"
|
||||
title="Trophy 2"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="hc-badge-container"
|
||||
>
|
||||
<img
|
||||
class="hc-badge"
|
||||
src="/api/icons/empty"
|
||||
title="Empty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
|
||||
<ul
|
||||
class="statistics"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
profile.followers
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
common.post
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div
|
||||
class="ds-number ds-number-size-x-large"
|
||||
>
|
||||
<p
|
||||
class="ds-text ds-number-count ds-text-size-x-large"
|
||||
style="margin-bottom: 0px;"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="ds-text ds-number-label ds-text-size-small ds-text-soft"
|
||||
>
|
||||
|
||||
common.comment
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
2
webapp/components/utils/isTouchDevice.js
Normal file
2
webapp/components/utils/isTouchDevice.js
Normal file
@ -0,0 +1,2 @@
|
||||
export const isTouchDevice = () =>
|
||||
'ontouchstart' in window || navigator.MaxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
|
||||
@ -17,9 +17,11 @@ export const locationFragment = (lang) => gql`
|
||||
fragment location on User {
|
||||
locationName
|
||||
location {
|
||||
id
|
||||
name: name${lang}
|
||||
lng
|
||||
lat
|
||||
distanceToMe
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -50,6 +52,19 @@ export const userCountsFragment = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const userTeaserFragment = (lang) => gql`
|
||||
${badgesFragment}
|
||||
${locationFragment(lang)}
|
||||
|
||||
fragment userTeaser on User {
|
||||
followedByCount
|
||||
contributionsCount
|
||||
commentedCount
|
||||
...badges
|
||||
...location
|
||||
}
|
||||
`
|
||||
|
||||
export const postFragment = gql`
|
||||
fragment post on Post {
|
||||
id
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
postFragment,
|
||||
commentFragment,
|
||||
groupFragment,
|
||||
userTeaserFragment,
|
||||
} from './Fragments'
|
||||
|
||||
export const profileUserQuery = (i18n) => {
|
||||
@ -125,7 +126,7 @@ export const mapUserQuery = (i18n) => {
|
||||
`
|
||||
}
|
||||
|
||||
export const notificationQuery = (_i18n) => {
|
||||
export const notificationQuery = () => {
|
||||
return gql`
|
||||
${userFragment}
|
||||
${commentFragment}
|
||||
@ -483,6 +484,18 @@ export const userDataQuery = (i18n) => {
|
||||
`
|
||||
}
|
||||
|
||||
export const userTeaserQuery = (i18n) => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
${userTeaserFragment(lang)}
|
||||
query ($id: ID!) {
|
||||
User(id: $id) {
|
||||
...userTeaser
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const setTrophyBadgeSelected = gql`
|
||||
mutation ($slot: Int!, $badgeId: ID) {
|
||||
setTrophyBadgeSelected(slot: $slot, badgeId: $badgeId) {
|
||||
|
||||
@ -636,6 +636,9 @@
|
||||
"localeSwitch": {
|
||||
"tooltip": "Sprache wählen"
|
||||
},
|
||||
"location": {
|
||||
"distance": "{distance} km von mir entfernt"
|
||||
},
|
||||
"login": {
|
||||
"email": "Deine E-Mail",
|
||||
"failure": "Fehlerhafte E-Mail-Adresse oder Passwort.",
|
||||
@ -1173,5 +1176,10 @@
|
||||
"newTermsAndConditions": "Neue Nutzungsbedingungen",
|
||||
"termsAndConditionsNewConfirm": "Ich habe die neuen Nutzungsbedingungen durchgelesen und stimme zu.",
|
||||
"termsAndConditionsNewConfirmText": "Bitte lies Dir die neuen Nutzungsbedingungen jetzt durch!"
|
||||
},
|
||||
"user-teaser": {
|
||||
"popover": {
|
||||
"open-profile": "Profil öffnen"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,6 +636,9 @@
|
||||
"localeSwitch": {
|
||||
"tooltip": "Choose language"
|
||||
},
|
||||
"location": {
|
||||
"distance": "{distance} km away from me"
|
||||
},
|
||||
"login": {
|
||||
"email": "Your E-mail",
|
||||
"failure": "Incorrect email address or password.",
|
||||
@ -1173,5 +1176,10 @@
|
||||
"newTermsAndConditions": "New Terms and Conditions",
|
||||
"termsAndConditionsNewConfirm": "I have read and agree to the new terms of conditions.",
|
||||
"termsAndConditionsNewConfirmText": "Please read the new terms of use now!"
|
||||
},
|
||||
"user-teaser": {
|
||||
"popover": {
|
||||
"open-profile": "Open profile"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,6 +636,9 @@
|
||||
"localeSwitch": {
|
||||
"tooltip": null
|
||||
},
|
||||
"location": {
|
||||
"distance": null
|
||||
},
|
||||
"login": {
|
||||
"email": "Su correo electrónico",
|
||||
"failure": "Dirección de correo electrónico o contraseña incorrecta.",
|
||||
@ -1173,5 +1176,10 @@
|
||||
"newTermsAndConditions": "Nuevos términos de uso",
|
||||
"termsAndConditionsNewConfirm": "He leído y acepto los nuevos términos de uso.",
|
||||
"termsAndConditionsNewConfirmText": "¡Por favor, lea los nuevos términos de uso ahora!"
|
||||
},
|
||||
"user-teaser": {
|
||||
"popover": {
|
||||
"open-profile": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,6 +636,9 @@
|
||||
"localeSwitch": {
|
||||
"tooltip": null
|
||||
},
|
||||
"location": {
|
||||
"distance": null
|
||||
},
|
||||
"login": {
|
||||
"email": "Votre mail",
|
||||
"failure": "Adresse mail ou mot de passe incorrect.",
|
||||
@ -1173,5 +1176,10 @@
|
||||
"newTermsAndConditions": "Nouvelles conditions générales",
|
||||
"termsAndConditionsNewConfirm": "J'ai lu et accepté les nouvelles conditions générales.",
|
||||
"termsAndConditionsNewConfirmText": "Veuillez lire les nouvelles conditions d'utilisation dès maintenant !"
|
||||
},
|
||||
"user-teaser": {
|
||||
"popover": {
|
||||
"open-profile": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,6 +636,9 @@
|
||||
"localeSwitch": {
|
||||
"tooltip": null
|
||||
},
|
||||
"location": {
|
||||
"distance": null
|
||||
},
|
||||
"login": {
|
||||
"email": "La tua email",
|
||||
"failure": null,
|
||||
@ -1173,5 +1176,10 @@
|
||||
"newTermsAndConditions": "Nuovi Termini e Condizioni",
|
||||
"termsAndConditionsNewConfirm": "Ho letto e accetto le nuove condizioni generali di contratto.",
|
||||
"termsAndConditionsNewConfirmText": "Si prega di leggere le nuove condizioni d'uso ora!"
|
||||
},
|
||||
"user-teaser": {
|
||||
"popover": {
|
||||
"open-profile": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,6 +636,9 @@
|
||||
"localeSwitch": {
|
||||
"tooltip": null
|
||||
},
|
||||
"location": {
|
||||
"distance": null
|
||||
},
|
||||
"login": {
|
||||
"email": "Uw E-mail",
|
||||
"failure": null,
|
||||
@ -1173,5 +1176,10 @@
|
||||
"newTermsAndConditions": null,
|
||||
"termsAndConditionsNewConfirm": null,
|
||||
"termsAndConditionsNewConfirmText": null
|
||||
},
|
||||
"user-teaser": {
|
||||
"popover": {
|
||||
"open-profile": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,6 +636,9 @@
|
||||
"localeSwitch": {
|
||||
"tooltip": null
|
||||
},
|
||||
"location": {
|
||||
"distance": null
|
||||
},
|
||||
"login": {
|
||||
"email": "Twój adres e-mail",
|
||||
"failure": null,
|
||||
@ -1173,5 +1176,10 @@
|
||||
"newTermsAndConditions": null,
|
||||
"termsAndConditionsNewConfirm": null,
|
||||
"termsAndConditionsNewConfirmText": null
|
||||
},
|
||||
"user-teaser": {
|
||||
"popover": {
|
||||
"open-profile": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,6 +636,9 @@
|
||||
"localeSwitch": {
|
||||
"tooltip": null
|
||||
},
|
||||
"location": {
|
||||
"distance": null
|
||||
},
|
||||
"login": {
|
||||
"email": "Seu email",
|
||||
"failure": "Endereço de e-mail ou senha incorretos.",
|
||||
@ -1173,5 +1176,10 @@
|
||||
"newTermsAndConditions": "Novos Termos e Condições",
|
||||
"termsAndConditionsNewConfirm": "Eu li e concordo com os novos termos de condições.",
|
||||
"termsAndConditionsNewConfirmText": "Por favor, leia os novos termos de uso agora!"
|
||||
},
|
||||
"user-teaser": {
|
||||
"popover": {
|
||||
"open-profile": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,6 +636,9 @@
|
||||
"localeSwitch": {
|
||||
"tooltip": null
|
||||
},
|
||||
"location": {
|
||||
"distance": null
|
||||
},
|
||||
"login": {
|
||||
"email": "Электронная почта",
|
||||
"failure": "Неверный адрес электронной почты или пароль.",
|
||||
@ -1173,5 +1176,10 @@
|
||||
"newTermsAndConditions": "Новые условия и положения",
|
||||
"termsAndConditionsNewConfirm": "Я прочитал(а) и согласен(на) с новыми условиями.",
|
||||
"termsAndConditionsNewConfirmText": "Пожалуйста, ознакомьтесь с новыми условиями использования!"
|
||||
},
|
||||
"user-teaser": {
|
||||
"popover": {
|
||||
"open-profile": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ocelot-social/maintenance",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"description": "Maintenance page for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-webapp",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"description": "ocelot.social Frontend",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user