mirror of
https://github.com/IT4Change/boilerplate-frontend.git
synced 2025-12-13 07:35:53 +00:00
Merge pull request #6 from IT4Change/more-github-workflows
feat(frontend): stylelint & workflow build storybook
This commit is contained in:
commit
bdec96c9ec
4
.github/file-filters.yml
vendored
4
.github/file-filters.yml
vendored
@ -11,3 +11,7 @@ frontend-test-build-code: &frontend-test-build-code
|
|||||||
|
|
||||||
frontend-test-build-docs: &frontend-test-build-docs
|
frontend-test-build-docs: &frontend-test-build-docs
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
|
- '.vuepress/'
|
||||||
|
|
||||||
|
frontend-test-build-storybook: &frontend-test-build-storybook
|
||||||
|
- '**/*'
|
||||||
@ -1,4 +1,4 @@
|
|||||||
name: "deploy:docs to github"
|
name: "frontend:deploy:docs to github"
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@ -1,4 +1,4 @@
|
|||||||
name: "test:build test code"
|
name: "frontend:test:build test code"
|
||||||
|
|
||||||
on: push
|
on: push
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
name: "test:build test docs"
|
name: "frontend:test:build test docs"
|
||||||
|
|
||||||
on: push
|
on: push
|
||||||
|
|
||||||
@ -30,5 +30,5 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Frontend | Build
|
- name: Frontend | Build Docs
|
||||||
run: npm install && npm run docs:build
|
run: npm install && npm run docs:build
|
||||||
34
.github/workflows/frontend.test.build.storybook.yml
vendored
Normal file
34
.github/workflows/frontend.test.build.storybook.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: "frontend:test:build test storybook"
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# only (but most important) job from this workflow required for pull requests
|
||||||
|
# check results serve as run conditions for all other jobs here
|
||||||
|
files-changed:
|
||||||
|
name: Detect File Changes - frontend-test-build-storybook
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
changes: ${{ steps.changes.outputs.frontend-test-build-storybook }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3.3.0
|
||||||
|
|
||||||
|
- name: Check for frontend file changes
|
||||||
|
uses: dorny/paths-filter@v2.11.1
|
||||||
|
id: changes
|
||||||
|
with:
|
||||||
|
token: ${{ github.token }}
|
||||||
|
filters: .github/file-filters.yml
|
||||||
|
list-files: shell
|
||||||
|
|
||||||
|
storybook:
|
||||||
|
if: needs.files-changed.outputs.changes == 'true'
|
||||||
|
name: Build Storybook - Frontend
|
||||||
|
needs: files-changed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Frontend | Build Storybook
|
||||||
|
run: npm install && npm run storybook:build
|
||||||
@ -1,4 +1,4 @@
|
|||||||
name: "test:lint code with defined linters"
|
name: "frontend:test:lint code with defined linters"
|
||||||
|
|
||||||
on: push
|
on: push
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
name: "test:unit test code with defined suites"
|
name: "frontend:test:unit test code with defined suites"
|
||||||
|
|
||||||
on: push
|
on: push
|
||||||
|
|
||||||
3
.github/workflows/test.lint.pr.yml
vendored
3
.github/workflows/test.lint.pr.yml
vendored
@ -9,7 +9,7 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
actions: write
|
statuses: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
@ -29,6 +29,7 @@ jobs:
|
|||||||
# Configure which scopes are allowed (newline delimited).
|
# Configure which scopes are allowed (newline delimited).
|
||||||
# Append a scope for each service here
|
# Append a scope for each service here
|
||||||
scopes: |
|
scopes: |
|
||||||
|
frontend
|
||||||
docker
|
docker
|
||||||
release
|
release
|
||||||
workflow
|
workflow
|
||||||
|
|||||||
9
.stylelintrc.json
Normal file
9
.stylelintrc.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"stylelint-config-standard",
|
||||||
|
"stylelint-config-standard-scss",
|
||||||
|
"stylelint-config-recommended-vue",
|
||||||
|
"stylelint-config-recess-order",
|
||||||
|
"stylelint-config-css-modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@
|
|||||||
[![vue-i18n][badge-vue-i18n-img]][badge-vue-i18n-href]
|
[![vue-i18n][badge-vue-i18n-img]][badge-vue-i18n-href]
|
||||||
[![eslint][badge-eslint-img]][badge-eslint-href]
|
[![eslint][badge-eslint-img]][badge-eslint-href]
|
||||||
[![remark-cli][badge-remark-cli-img]][badge-remark-cli-href]
|
[![remark-cli][badge-remark-cli-img]][badge-remark-cli-href]
|
||||||
|
[![stylelint][badge-stylelint-img]][badge-stylelint-href]
|
||||||
[![vitest][badge-vitest-img]][badge-vitest-href]
|
[![vitest][badge-vitest-img]][badge-vitest-href]
|
||||||
[![storybook][badge-storybook-img]][badge-storybook-href]
|
[![storybook][badge-storybook-img]][badge-storybook-href]
|
||||||
[![vuepress][badge-vuepress-img]][badge-vuepress-href]
|
[![vuepress][badge-vuepress-img]][badge-vuepress-href]
|
||||||
@ -21,7 +22,7 @@ To be able to build this project you need `nodejs`, `npm` and optional `docker`.
|
|||||||
|
|
||||||
The project uses `vite` as builder, `vike` to do the SSR. The design framework is `vuetify` which requires the frontend framework `vue3`. For localization `vue-i18n` is used; Session storage is handled with `pinia`.
|
The project uses `vite` as builder, `vike` to do the SSR. The design framework is `vuetify` which requires the frontend framework `vue3`. For localization `vue-i18n` is used; Session storage is handled with `pinia`.
|
||||||
|
|
||||||
Testing is done with `vitest` and code style is enforced with `eslint` and `remark-cli`.
|
Testing is done with `vitest` and code style is enforced with `eslint`, `remark-cli` and `stylelint`.
|
||||||
|
|
||||||
This projects utilizes `storybook` to develop frontend components and `vuepress` for static documentation generation.
|
This projects utilizes `storybook` to develop frontend components and `vuepress` for static documentation generation.
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ The following commands are available:
|
|||||||
| `npm run test:lint` | Run all linters |
|
| `npm run test:lint` | Run all linters |
|
||||||
| `npm run test:lint:eslint` | Run linter eslint |
|
| `npm run test:lint:eslint` | Run linter eslint |
|
||||||
| `npm run test:lint:remark` | Run linter remark |
|
| `npm run test:lint:remark` | Run linter remark |
|
||||||
|
| `npm run test:lint:style` | Run linter stylelint |
|
||||||
| `npm run test:unit` | Run all unit tests and generate coverage report |
|
| `npm run test:unit` | Run all unit tests and generate coverage report |
|
||||||
| `npm run test:unit:dev` | Run all unit tests in watch mode |
|
| `npm run test:unit:dev` | Run all unit tests in watch mode |
|
||||||
| `npm test` | Run all tests & linters |
|
| `npm test` | Run all tests & linters |
|
||||||
@ -122,6 +124,9 @@ Currently none
|
|||||||
[badge-remark-cli-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies%5B%27remark-cli%27%5D&label=remark-cli&color=yellow
|
[badge-remark-cli-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies%5B%27remark-cli%27%5D&label=remark-cli&color=yellow
|
||||||
[badge-remark-cli-href]: https://remark.js.org/
|
[badge-remark-cli-href]: https://remark.js.org/
|
||||||
|
|
||||||
|
[badge-stylelint-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies.stylelint&label=stylelint&color=yellow
|
||||||
|
[badge-stylelint-href]: https://stylelint.io/
|
||||||
|
|
||||||
[badge-vitest-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies.vitest&label=vitest&color=yellow
|
[badge-vitest-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies.vitest&label=vitest&color=yellow
|
||||||
[badge-vitest-href]: https://vitest.dev/
|
[badge-vitest-href]: https://vitest.dev/
|
||||||
|
|
||||||
|
|||||||
1243
package-lock.json
generated
1243
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -40,9 +40,10 @@
|
|||||||
"storybook": "storybook dev -p 6006",
|
"storybook": "storybook dev -p 6006",
|
||||||
"storybook:build": "storybook build -o build/storybook",
|
"storybook:build": "storybook build -o build/storybook",
|
||||||
"storybook:test": "test-storybook",
|
"storybook:test": "test-storybook",
|
||||||
"test:lint": "npm run test:lint:eslint && npm run test:lint:remark",
|
"test:lint": "npm run test:lint:eslint && npm run test:lint:remark && npm run test:lint:style",
|
||||||
"test:lint:eslint": "eslint --ext .vue,.ts,.tsx,.js,.jsx,.json,.yml,.yaml --max-warnings 0 --ignore-path .gitignore .",
|
"test:lint:eslint": "eslint --ext .vue,.ts,.tsx,.js,.jsx,.json,.yml,.yaml --max-warnings 0 --ignore-path .gitignore .",
|
||||||
"test:lint:remark": "remark . --quiet --frail",
|
"test:lint:remark": "remark . --quiet --frail",
|
||||||
|
"test:lint:style": "stylelint --max-warnings 0 --ignore-path .gitignore \"**/*.{css,scss,vue,vuex}\"",
|
||||||
"test:unit": "npm run test:unit:dev -- run --coverage",
|
"test:unit": "npm run test:unit:dev -- run --coverage",
|
||||||
"test:unit:dev": "vitest",
|
"test:unit:dev": "vitest",
|
||||||
"test": "npm run test:lint && npm run test:unit",
|
"test": "npm run test:lint && npm run test:unit",
|
||||||
@ -112,6 +113,12 @@
|
|||||||
"remark-preset-lint-markdown-style-guide": "^5.1.3",
|
"remark-preset-lint-markdown-style-guide": "^5.1.3",
|
||||||
"remark-preset-lint-recommended": "^6.1.3",
|
"remark-preset-lint-recommended": "^6.1.3",
|
||||||
"storybook": "^7.5.3",
|
"storybook": "^7.5.3",
|
||||||
|
"stylelint": "^15.11.0",
|
||||||
|
"stylelint-config-css-modules": "^4.3.0",
|
||||||
|
"stylelint-config-recess-order": "^4.4.0",
|
||||||
|
"stylelint-config-recommended-vue": "^1.5.0",
|
||||||
|
"stylelint-config-standard": "^34.0.0",
|
||||||
|
"stylelint-config-standard-scss": "^11.1.0",
|
||||||
"vitest": "^0.34.6",
|
"vitest": "^0.34.6",
|
||||||
"vuepress": "^2.0.0-rc.0"
|
"vuepress": "^2.0.0-rc.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,9 +24,11 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@ -38,20 +40,23 @@ a {
|
|||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 20px;
|
|
||||||
border-left: 2px solid #eee;
|
|
||||||
padding-bottom: 50px;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
|
||||||
.navigation {
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
flex-shrink: 0;
|
padding-bottom: 50px;
|
||||||
|
border-left: 2px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
line-height: 1.8em;
|
line-height: 1.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|||||||
@ -12,6 +12,7 @@ const pageContext = usePageContext()
|
|||||||
a {
|
a {
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.active {
|
a.active {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
code {
|
code {
|
||||||
|
padding: 3px 5px;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
background-color: #eaeaea;
|
background-color: #eaeaea;
|
||||||
padding: 3px 5px;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,30 +1,40 @@
|
|||||||
.storybook-button {
|
.storybook-button {
|
||||||
|
display: inline-block;
|
||||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 3em;
|
border-radius: 3em;
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line selector-class-pattern */
|
||||||
.storybook-button--primary {
|
.storybook-button--primary {
|
||||||
color: white;
|
color: white;
|
||||||
background-color: #1ea7fd;
|
background-color: #1ea7fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line selector-class-pattern */
|
||||||
.storybook-button--secondary {
|
.storybook-button--secondary {
|
||||||
color: #333;
|
color: #333;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
|
box-shadow: rgb(0 0 0 / 15%) 0 0 0 1px inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line selector-class-pattern */
|
||||||
.storybook-button--small {
|
.storybook-button--small {
|
||||||
font-size: 12px;
|
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line selector-class-pattern */
|
||||||
.storybook-button--medium {
|
.storybook-button--medium {
|
||||||
font-size: 14px;
|
|
||||||
padding: 11px 20px;
|
padding: 11px 20px;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line selector-class-pattern */
|
||||||
.storybook-button--large {
|
.storybook-button--large {
|
||||||
font-size: 16px;
|
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
.storybook-header {
|
.storybook-header {
|
||||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
padding: 15px 20px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
padding: 15px 20px;
|
||||||
|
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
border-bottom: 1px solid rgb(0 0 0 / 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.storybook-header svg {
|
.storybook-header svg {
|
||||||
@ -13,11 +13,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.storybook-header h1 {
|
.storybook-header h1 {
|
||||||
font-weight: 700;
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 1;
|
|
||||||
margin: 6px 0 6px 10px;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
margin: 6px 0 6px 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +26,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.storybook-header .welcome {
|
.storybook-header .welcome {
|
||||||
color: #333;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
.storybook-page {
|
.storybook-page {
|
||||||
|
max-width: 600px;
|
||||||
|
padding: 48px 20px;
|
||||||
|
margin: 0 auto;
|
||||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
padding: 48px 20px;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 600px;
|
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.storybook-page h2 {
|
.storybook-page h2 {
|
||||||
font-weight: 700;
|
|
||||||
font-size: 32px;
|
|
||||||
line-height: 1;
|
|
||||||
margin: 0 0 4px;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,8 +22,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.storybook-page a {
|
.storybook-page a {
|
||||||
text-decoration: none;
|
|
||||||
color: #1ea7fd;
|
color: #1ea7fd;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.storybook-page ul {
|
.storybook-page ul {
|
||||||
@ -37,31 +37,31 @@
|
|||||||
|
|
||||||
.storybook-page .tip {
|
.storybook-page .tip {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-radius: 1em;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
background: #e7fdd8;
|
|
||||||
color: #66bf3c;
|
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 12px;
|
||||||
|
color: #66bf3c;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
background: #e7fdd8;
|
||||||
|
border-radius: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.storybook-page .tip-wrapper {
|
.storybook-page .tip-wrapper {
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.storybook-page .tip-wrapper svg {
|
.storybook-page .tip-wrapper svg {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 12px;
|
|
||||||
width: 12px;
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin-top: 3px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
margin-top: 3px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.storybook-page .tip-wrapper svg path {
|
.storybook-page .tip-wrapper svg path {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user