mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' into dependabot/npm_and_yarn/backend/eslint-plugin-n-16.6.2
This commit is contained in:
commit
74a3a7a2cc
2
.github/workflows/check-documentation.yml
vendored
2
.github/workflows/check-documentation.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Check for markdown file changes
|
||||
uses: dorny/paths-filter@v2.11.1
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
|
||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@ -253,7 +253,7 @@ jobs:
|
||||
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
- name: package-version-to-git-tag + build number
|
||||
uses: pkgdeps/git-tag-action@v2
|
||||
uses: pkgdeps/git-tag-action@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github_repo: ${{ github.repository }}
|
||||
|
||||
3
.github/workflows/test-backend.yml
vendored
3
.github/workflows/test-backend.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for backend file changes
|
||||
uses: dorny/paths-filter@v2.11.1
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
@ -108,6 +108,7 @@ jobs:
|
||||
- name: backend | copy env files
|
||||
run: |
|
||||
cp webapp/.env.template webapp/.env
|
||||
cp frontend/.env.dist frontend/.env
|
||||
cp backend/.env.template backend/.env
|
||||
|
||||
- name: backend | docker-compose
|
||||
|
||||
1
.github/workflows/test-e2e.yml
vendored
1
.github/workflows/test-e2e.yml
vendored
@ -13,6 +13,7 @@ jobs:
|
||||
- name: Copy env files
|
||||
run: |
|
||||
cp webapp/.env.template webapp/.env
|
||||
cp frontend/.env.dist frontend/.env
|
||||
cp backend/.env.template backend/.env
|
||||
|
||||
- name: Build docker images
|
||||
|
||||
3
.github/workflows/test-webapp.yml
vendored
3
.github/workflows/test-webapp.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for frontend file changes
|
||||
uses: dorny/paths-filter@v2.11.1
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
@ -90,6 +90,7 @@ jobs:
|
||||
- name: Copy env files
|
||||
run: |
|
||||
cp webapp/.env.template webapp/.env
|
||||
cp frontend/.env.dist frontend/.env
|
||||
cp backend/.env.template backend/.env
|
||||
|
||||
- name: backend | docker-compose
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -15,8 +15,13 @@ cypress/videos
|
||||
cypress/screenshots/
|
||||
cypress.env.json
|
||||
|
||||
.vuepress/.cache/
|
||||
.vuepress/.temp/
|
||||
.vuepress/dist/
|
||||
|
||||
|
||||
!.gitkeep
|
||||
**/coverage
|
||||
|
||||
release/
|
||||
*~
|
||||
*~
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { viteBundler } from '@vuepress/bundler-vite'
|
||||
|
||||
import meta from './config/meta'
|
||||
import theme from './config/theme'
|
||||
import plugins from './config/plugins'
|
||||
|
||||
export default defineUserConfig({
|
||||
pagePatterns: ['**/*.md', '!.vuepress', '!node_modules', '!backend/node_modules', '!webapp/node_modules', '!deployment/src/old'],
|
||||
bundler: viteBundler(),
|
||||
...meta,
|
||||
theme,
|
||||
plugins,
|
||||
})
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import { searchProPlugin } from 'vuepress-plugin-search-pro'
|
||||
|
||||
export default [
|
||||
searchProPlugin({
|
||||
indexContent: true,
|
||||
autoSuggestions: true,
|
||||
customFields: [
|
||||
{
|
||||
getter: (page) => page.frontmatter.category,
|
||||
formatter: "Category: $content",
|
||||
},
|
||||
{
|
||||
getter: (page) => page.frontmatter.tag,
|
||||
formatter: "Tag: $content",
|
||||
},
|
||||
],
|
||||
})
|
||||
]
|
||||
@ -27,6 +27,20 @@ export default hopeTheme({
|
||||
mdEnhance: {
|
||||
tabs: true,
|
||||
imgSize: true
|
||||
},
|
||||
searchPro: {
|
||||
indexContent: true,
|
||||
autoSuggestions: true,
|
||||
customFields: [
|
||||
{
|
||||
getter: (page) => page.frontmatter.category,
|
||||
formatter: "Category: $content",
|
||||
},
|
||||
{
|
||||
getter: (page) => page.frontmatter.tag,
|
||||
formatter: "Tag: $content",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# CONTRIBUTING
|
||||
|
||||
Thank you so much for thinking of contributing to the <!-- [ -->ocelot.social<!-- ](https://ocelot.social) --> project! It's awesome you're here, we really appreciate it. :-\)
|
||||
Thank you so much for thinking of contributing to the [ocelot.social](https://ocelot.social) project! It's awesome you're here, we really appreciate it. :-\)
|
||||
|
||||
## Getting Set Up
|
||||
|
||||
|
||||
15
README.md
15
README.md
@ -6,13 +6,12 @@
|
||||
[](https://discord.gg/AJSX9DCSUA)
|
||||
[](https://www.codetriage.com/ocelot-social-community/ocelot-social)
|
||||
|
||||
<!-- [ -->Ocelot.social<!-- ](<https://ocelot.social>) --> is free and open source software program code to run social networks. Its development is supported by a community of programmers and interested network operators.
|
||||
[Ocelot.social](https://ocelot.social) is free and open source software program code to run social networks. Its development is supported by a community of programmers and interested network operators.
|
||||
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<p align="center">
|
||||
<!-- <a href="https://ocelot.social" target="_blank"> -->
|
||||
<img src="https://raw.githubusercontent.com/Ocelot-Social-Community/Ocelot-Social/master/webapp/static/img/custom/logo-squared.svg" alt="ocelot.social" width="40%" height="40%">
|
||||
<!-- </a> -->
|
||||
<!-- <p align="center"> -->
|
||||
<p style="text-align: center;">
|
||||
<a href="https://ocelot.social" target="_blank"><img src="https://raw.githubusercontent.com/Ocelot-Social-Community/Ocelot-Social/master/webapp/static/img/custom/logo-squared.svg" alt="ocelot.social" width="40%" height="40%"></a>
|
||||
</p>
|
||||
<!-- markdownlint-enable MD033 -->
|
||||
|
||||
@ -69,7 +68,7 @@ Try out our live demo network, see [here](#live-demo-and-developer-logins).
|
||||
If you're wondering how you could help, there are plenty of ways, e.g.:
|
||||
|
||||
- Spread the good word about ocelot.social to make it more popular:
|
||||
- Add the link <!-- [ -->ocelot.social<!-- ](https://ocelot.social)--> to your website.
|
||||
- Add the link [ocelot.social](https://ocelot.social) to your website.
|
||||
- Give ocelot.social a Like at <https://alternativeto.net/software/ocelot-social/>.
|
||||
- Star our project on GitHub at <https://github.com/Ocelot-Social-Community/Ocelot-Social/>.
|
||||
- Promote it on your social networks.
|
||||
@ -140,11 +139,11 @@ $ cd Ocelot-Social
|
||||
|
||||
### Live Demo and Developer Logins
|
||||
|
||||
**Try out our deployed <!-- [ -->development environment<!--](https://stage.ocelot.social)-->.**
|
||||
**Try out our deployed [development environment](https://stage.ocelot.social).**
|
||||
|
||||
Visit our staging networks:
|
||||
|
||||
- central staging network: <!-- [ -->stage.ocelot.social<!-- ](https://stage.ocelot.social)-->
|
||||
- central staging network: [stage.ocelot.social](https://stage.ocelot.social)
|
||||
<!-- - rebranded staging network: [rebrand.ocelot.social](https://stage.ocelot.social). -->
|
||||
|
||||
#### Login
|
||||
|
||||
@ -39,6 +39,8 @@ module.exports = defineConfig({
|
||||
supportFile: "cypress/support/e2e.js",
|
||||
retries: 0,
|
||||
video: false,
|
||||
viewportHeight: 720,
|
||||
viewportWidth: 1290,
|
||||
setupNodeEvents,
|
||||
},
|
||||
env: parsed
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { Then } from "@badeball/cypress-cucumber-preprocessor";
|
||||
import 'cypress-network-idle';
|
||||
|
||||
Then("I click the checkbox show donations progress bar and save", () => {
|
||||
cy.get("#showDonations").click()
|
||||
cy.get(".donations-info-button").click()
|
||||
cy.waitForNetworkIdle(2000)
|
||||
})
|
||||
|
||||
@ -9,20 +9,18 @@ This repository is an in use template to rebrand, configure, and deploy [ocelot.
|
||||
The forked original repository is [stage.ocelot.social](https://github.com/Ocelot-Social-Community/stage.ocelot.social).
|
||||
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<p align="center">
|
||||
<!-- <a href="https://ocelot.social" target="_blank"> -->
|
||||
<img src="../webapp/static/img/custom/logo-squared.svg" alt="ocelot.social" width="40%" height="40%">
|
||||
<!-- </a> -->
|
||||
<p style="text-align: center;">
|
||||
<a href="https://ocelot.social" target="_blank"><img src="https://raw.githubusercontent.com/Ocelot-Social-Community/Ocelot-Social/master/webapp/static/img/custom/logo-squared.svg" alt="ocelot.social" width="40%" height="40%"></a>
|
||||
</p>
|
||||
<!-- markdownlint-enable MD033 -->
|
||||
|
||||
## Live demo
|
||||
|
||||
__Try out our deployed <!-- [ -->development environment<!-- ](https://stage.ocelot.social)-->.__
|
||||
__Try out our deployed [development environment](https://stage.ocelot.social).__
|
||||
|
||||
Visit our staging networks:
|
||||
|
||||
- central staging network: <!-- [ -->stage.ocelot.social<!-- ](https://stage.ocelot.social)-->
|
||||
- central staging network: [stage.ocelot.social](https://stage.ocelot.social)
|
||||
<!-- - rebranded staging network: [rebrand.ocelot.social](https://stage.ocelot.social). -->
|
||||
|
||||
Logins:
|
||||
|
||||
@ -49,6 +49,49 @@ Skip `Patch & Minor Version Upgrades` for now.
|
||||
You don't need a 1-click app. Our helmet script will install the required NGINXs.
|
||||
Therefore, skip this step as well.
|
||||
|
||||
For a 1-click Kubernetes Dashboard or alternatives, follow the next steps.
|
||||
|
||||
## Install Kubernetes Dashboard
|
||||
|
||||
We recommend installing a Kubernetes Dashboard, as DigitalOcean no longer offers a pre-installed dashboard.
|
||||
|
||||
- 1-click-deployment of [Kubernetes Dashboard on DigitalOcean marketplace](https://marketplace.digitalocean.com/apps/kubernetes-dashboard)
|
||||
|
||||
There you will also find a section entitled `Getting Started`, which describes how you can log in from your local computer.
|
||||
|
||||
Very short description:
|
||||
|
||||
### In your DigitalOcean Account
|
||||
|
||||
For authentication, download the current cluster configuration file from DigitalOcean.
|
||||
|
||||
### In Terminal
|
||||
|
||||
Set the context of the cluster by command:
|
||||
|
||||
```bash
|
||||
$ kubectl config use-context <context-name>
|
||||
```
|
||||
|
||||
Port-forward the Kubernetes Dashboard to your local machine:
|
||||
|
||||
```bash
|
||||
# save pod name
|
||||
$ export POD_NAME=$(kubectl get pods -n kubernetes-dashboard -l "app.kubernetes.io/name=kubernetes-dashboard,app.kubernetes.io/instance=kubernetes-dashboard" -o jsonpath="{.items[0].metadata.name}")
|
||||
# forward port
|
||||
$ kubectl -n kubernetes-dashboard port-forward $POD_NAME 8443:8443
|
||||
```
|
||||
|
||||
### Log-In to Kubernetes Dashboard
|
||||
|
||||
Access the URL in your local web browser at `https://127.0.0.1:8443/`, and log in using your Kubernetes cluster credentials – downloaded config file. You may encounter a certificate warning, so make sure to override it.
|
||||
|
||||
## Alternatives to Kubernetes Dashboard
|
||||
|
||||
DigitalOcean has a website about Kubernetes Dashboard and alternatives:
|
||||
|
||||
- <https://www.digitalocean.com/community/conceptual-articles/kubernetes-visualization-tools?mkt_tok=MTEzLURUTi0yNjYAAAGQ0YS-wbZaWn5th-m86-fM7vgiLvxNipWpAsUrgd2z4YgiMB0aRgCIDYEiC0Y2c0H9tBsICZQ5ORKgssOgeSjOKSEfN3i7xUpzqXbdZiYxNL2Q>
|
||||
|
||||
## DNS Configuration
|
||||
|
||||
There are the following two ways to set up the DNS.
|
||||
|
||||
@ -21,6 +21,26 @@ services:
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./webapp:/app
|
||||
|
||||
########################################################
|
||||
# FRONTEND #############################################
|
||||
########################################################
|
||||
frontend:
|
||||
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||
image: ocelotsocialnetwork/frontend:local-development
|
||||
build:
|
||||
target: development
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
ports:
|
||||
# port required for npm run dev
|
||||
- 24678:24678
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- frontend_node_modules:/app/node_modules
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./frontend:/app
|
||||
|
||||
########################################################
|
||||
# BACKEND ##############################################
|
||||
########################################################
|
||||
@ -77,4 +97,5 @@ services:
|
||||
|
||||
volumes:
|
||||
webapp_node_modules:
|
||||
frontend_node_modules:
|
||||
backend_node_modules:
|
||||
|
||||
@ -39,6 +39,36 @@ services:
|
||||
env_file:
|
||||
- ./webapp/.env
|
||||
|
||||
########################################################
|
||||
# FRONTEND #############################################
|
||||
########################################################
|
||||
frontend:
|
||||
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||
image: ocelotsocialnetwork/frontend:local-production
|
||||
container_name: frontend
|
||||
build:
|
||||
context: ./frontend
|
||||
target: production
|
||||
networks:
|
||||
- external-net
|
||||
depends_on:
|
||||
- backend
|
||||
ports:
|
||||
- 3002:3002
|
||||
environment:
|
||||
# Envs used in Dockerfile
|
||||
# - DOCKER_WORKDIR="/app"
|
||||
# - PORT="3002"
|
||||
- BUILD_DATE
|
||||
- BUILD_VERSION
|
||||
- BUILD_COMMIT
|
||||
- NODE_ENV="production"
|
||||
# Application only envs
|
||||
#- HOST=0.0.0.0 # This is nuxt specific, alternative value is HOST=webapp
|
||||
#- GRAPHQL_URI=http://backend:4000
|
||||
env_file:
|
||||
- ./frontend/.env
|
||||
|
||||
########################################################
|
||||
# BACKEND ##############################################
|
||||
########################################################
|
||||
|
||||
5
frontend/.dockerignore
Normal file
5
frontend/.dockerignore
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
build/
|
||||
coverage/
|
||||
.vuepress/.temp/
|
||||
.vuepress/.cache/
|
||||
5
frontend/.env.dist
Normal file
5
frontend/.env.dist
Normal file
@ -0,0 +1,5 @@
|
||||
# META
|
||||
PUBLIC_ENV__META__BASE_URL="http://localhost:3000"
|
||||
PUBLIC_ENV__META__DEFAULT_AUTHOR="IT Team 4 Change"
|
||||
PUBLIC_ENV__META__DEFAULT_DESCRIPTION="IT4C Frontend Boilerplate"
|
||||
PUBLIC_ENV__META__DEFAULT_TITLE="IT4C"
|
||||
5
frontend/.eslintignore
Normal file
5
frontend/.eslintignore
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
build/
|
||||
coverage/
|
||||
.storybook/
|
||||
.vuepress/
|
||||
182
frontend/.eslintrc.cjs
Normal file
182
frontend/.eslintrc.cjs
Normal file
@ -0,0 +1,182 @@
|
||||
// eslint-disable-next-line import/no-commonjs
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
'standard',
|
||||
'eslint:recommended',
|
||||
'plugin:@eslint-community/eslint-comments/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
'plugin:promise/recommended',
|
||||
'plugin:security/recommended-legacy',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:@intlify/vue-i18n/recommended',
|
||||
'plugin:storybook/recommended',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'import', 'promise', 'security', 'vue', 'storybook'],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: true,
|
||||
node: true,
|
||||
},
|
||||
'vue-i18n': {
|
||||
localeDir: './src/locales/*.json',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'error',
|
||||
'no-debugger': 'error',
|
||||
camelcase: 'error',
|
||||
indent: ['error', 2],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
semi: ['error', 'never'],
|
||||
// This makes sure our vike router does not throw errors
|
||||
'vue/multi-word-component-names': [
|
||||
'error',
|
||||
{
|
||||
ignores: ['+Page'],
|
||||
},
|
||||
],
|
||||
// Optional eslint-comments rule
|
||||
'@eslint-community/eslint-comments/no-unused-disable': 'error',
|
||||
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
|
||||
// import
|
||||
'import/export': 'error',
|
||||
'import/no-deprecated': 'error',
|
||||
'import/no-empty-named-blocks': 'error',
|
||||
'import/no-extraneous-dependencies': 'error',
|
||||
'import/no-mutable-exports': 'error',
|
||||
'import/no-unused-modules': 'error',
|
||||
'import/no-named-as-default': 'error',
|
||||
'import/no-named-as-default-member': 'error',
|
||||
'import/no-amd': 'error',
|
||||
'import/no-commonjs': 'error',
|
||||
'import/no-import-module-exports': 'error',
|
||||
'import/no-nodejs-modules': 'off',
|
||||
'import/unambiguous': 'off', // not compatible with scriptless vue files
|
||||
'import/default': 'error',
|
||||
'import/named': 'error',
|
||||
'import/namespace': 'error',
|
||||
'import/no-absolute-path': 'error',
|
||||
'import/no-cycle': 'error',
|
||||
'import/no-dynamic-require': 'error',
|
||||
'import/no-internal-modules': 'off',
|
||||
'import/no-relative-packages': 'error',
|
||||
'import/no-relative-parent-imports': [
|
||||
'error',
|
||||
{ ignore: ['#[src,root,components,pages,assets,layouts,stores,plugins,context,types]/*'] },
|
||||
],
|
||||
'import/no-self-import': 'error',
|
||||
'import/no-unresolved': 'error',
|
||||
'import/no-useless-path-segments': 'error',
|
||||
'import/no-webpack-loader-syntax': 'error',
|
||||
'import/consistent-type-specifier-style': 'error',
|
||||
'import/exports-last': 'off',
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'never',
|
||||
{
|
||||
json: 'always',
|
||||
},
|
||||
],
|
||||
'import/first': 'error',
|
||||
'import/group-exports': 'off',
|
||||
'import/newline-after-import': 'error',
|
||||
'import/no-anonymous-default-export': 'off', // todo - consider to enable again
|
||||
'import/no-default-export': 'off', // incompatible with vite & vike
|
||||
'import/no-duplicates': 'error',
|
||||
'import/no-named-default': 'error',
|
||||
'import/no-namespace': 'error',
|
||||
'import/no-unassigned-import': 'error',
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
'newlines-between': 'always',
|
||||
alphabetize: {
|
||||
order: 'asc', // sort in ascending order. Options: ["ignore", "asc", "desc"]
|
||||
caseInsensitive: true, // ignore case. Options: [true, false]
|
||||
},
|
||||
distinctGroup: true,
|
||||
},
|
||||
],
|
||||
'import/prefer-default-export': 'off',
|
||||
// promise
|
||||
'promise/catch-or-return': 'error',
|
||||
'promise/no-return-wrap': 'error',
|
||||
'promise/param-names': 'error',
|
||||
'promise/always-return': 'error',
|
||||
'promise/no-native': 'off',
|
||||
'promise/no-nesting': 'warn',
|
||||
'promise/no-promise-in-callback': 'warn',
|
||||
'promise/no-callback-in-promise': 'warn',
|
||||
'promise/avoid-new': 'warn',
|
||||
'promise/no-new-statics': 'error',
|
||||
'promise/no-return-in-finally': 'warn',
|
||||
'promise/valid-params': 'warn',
|
||||
'promise/prefer-await-to-callbacks': 'error',
|
||||
'promise/no-multiple-resolved': 'error',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json', '**/tsconfig.json'],
|
||||
ecmaVersion: 'latest',
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'plugin:@typescript-eslint/strict',
|
||||
],
|
||||
rules: {
|
||||
// allow explicitly defined dangling promises
|
||||
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
|
||||
'no-void': ['error', { allowAsStatement: true }],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['!*.json'],
|
||||
plugins: ['prettier'],
|
||||
extends: ['plugin:prettier/recommended'],
|
||||
rules: {
|
||||
'prettier/prettier': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.json'],
|
||||
plugins: ['json'],
|
||||
extends: ['plugin:json/recommended-with-comments'],
|
||||
},
|
||||
{
|
||||
files: ['*.vue'],
|
||||
plugins: ['vuetify'],
|
||||
extends: ['plugin:vuetify/recommended'],
|
||||
},
|
||||
{
|
||||
files: ['*.[test,spec].[tj]s'],
|
||||
plugins: ['vitest'],
|
||||
extends: ['plugin:vitest/all'],
|
||||
},
|
||||
{
|
||||
files: ['*.yaml', '*.yml'],
|
||||
parser: 'yaml-eslint-parser',
|
||||
plugins: ['yml'],
|
||||
extends: ['plugin:yml/prettier'],
|
||||
},
|
||||
],
|
||||
}
|
||||
1
frontend/.github/.remarkignore
vendored
Normal file
1
frontend/.github/.remarkignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*
|
||||
13
frontend/.github/ISSUE_TEMPLATE.md
vendored
Normal file
13
frontend/.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
|
||||
|
||||
<!--
|
||||
Please take a look at the issue templates at https://github.com/[ORGA/USER]/[REPO]/issues/new/choose
|
||||
before submitting a new issue. Following one of the issue templates will ensure maintainers can route your request efficiently.
|
||||
|
||||
Thanks!
|
||||
-->
|
||||
|
||||
## 💬 Issue
|
||||
<!-- Describe your Issue in detail. -->
|
||||
|
||||
<!-- Attach screenshots and drawings if needed. -->
|
||||
10
frontend/.github/ISSUE_TEMPLATE/bug.md
vendored
Normal file
10
frontend/.github/ISSUE_TEMPLATE/bug.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: 🐛 Bug
|
||||
about: Create a report to help us improve
|
||||
labels: bug
|
||||
title: 🐛 [Bug]
|
||||
---
|
||||
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
|
||||
|
||||
## 🐛 Bug
|
||||
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the bug is.-->
|
||||
10
frontend/.github/ISSUE_TEMPLATE/devops.md
vendored
Normal file
10
frontend/.github/ISSUE_TEMPLATE/devops.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: 💥 DevOp
|
||||
about: Help us manage our deployed Software.
|
||||
labels: devops
|
||||
title: 💥 [DevOps]
|
||||
---
|
||||
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
|
||||
|
||||
## 💥 DevOps
|
||||
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the problem is.-->
|
||||
13
frontend/.github/ISSUE_TEMPLATE/epic.md
vendored
Normal file
13
frontend/.github/ISSUE_TEMPLATE/epic.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
name: 🌟 Epic
|
||||
about: Define a big development Step
|
||||
labels: epic
|
||||
title: 🌟 [EPIC]
|
||||
---
|
||||
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
|
||||
|
||||
<!-- THIS ISSUE-TYPE IS NOT FOR YOU! -->
|
||||
<!-- Proceed only if you know what you are doing - have a chat with Project's Team first -->
|
||||
|
||||
## 🌟 EPIC
|
||||
<!-- Describe your Epic in detail. Include screenshots and drawings -->
|
||||
10
frontend/.github/ISSUE_TEMPLATE/feature.md
vendored
Normal file
10
frontend/.github/ISSUE_TEMPLATE/feature.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: 🚀 Feature
|
||||
about: Suggest an idea for this project
|
||||
labels: feature
|
||||
title: 🚀 [Feature]
|
||||
---
|
||||
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
|
||||
|
||||
## 🚀 Feature
|
||||
<!-- Give a short summary of the Feature. Use Screenshots if you want. -->
|
||||
13
frontend/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
13
frontend/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
name: 💬 Question
|
||||
about: If you need help understanding our Software.
|
||||
labels: question
|
||||
title: 💬 [Question]
|
||||
---
|
||||
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
|
||||
|
||||
<!-- Question the project's team -->
|
||||
<!-- If you need an answer right away, consider to take other means of communication with the project's team -->
|
||||
|
||||
## 💬 Question
|
||||
<!-- Describe your Question in detail. Include screenshots and drawings if needed. -->
|
||||
10
frontend/.github/ISSUE_TEMPLATE/refactor.md
vendored
Normal file
10
frontend/.github/ISSUE_TEMPLATE/refactor.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: 🔧 Refactor
|
||||
about: Help us improve our code by refactoring it.
|
||||
labels: refactor
|
||||
title: 🔧 [Refactor]
|
||||
---
|
||||
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
|
||||
|
||||
## 🔧 Refactor
|
||||
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the problem is.-->
|
||||
13
frontend/.github/ISSUE_TEMPLATE/release.md
vendored
Normal file
13
frontend/.github/ISSUE_TEMPLATE/release.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
name: 🎂 Release
|
||||
about: Define a Release
|
||||
labels: release
|
||||
title: 🎂 [RELEASE]
|
||||
---
|
||||
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
|
||||
|
||||
<!-- THIS ISSUE-TYPE IS NOT FOR YOU! -->
|
||||
<!-- Proceed only if you know what you are doing - have a chat with Project's Team first -->
|
||||
|
||||
## 🎂 RELEASE
|
||||
<!-- Describe your Release in detail. Include screenshots and drawings -->
|
||||
15
frontend/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
15
frontend/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
|
||||
|
||||
## 🍰 Pullrequest
|
||||
<!-- Describe the Pullrequest. Use Screenshots if possible. -->
|
||||
|
||||
### Issues
|
||||
<!-- Which Issues does this fix, which are related?
|
||||
- fixes #XXX
|
||||
- relates #XXX
|
||||
-->
|
||||
- None
|
||||
|
||||
### Todo
|
||||
<!-- In case some parts are still missing, list them here. -->
|
||||
- [X] None
|
||||
26
frontend/.github/dependabot.yml
vendored
Normal file
26
frontend/.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
- package-ecosystem: docker
|
||||
directory: "/"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
17
frontend/.github/file-filters.yml
vendored
Normal file
17
frontend/.github/file-filters.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# These file filter patterns are used by the action https://github.com/dorny/paths-filter
|
||||
|
||||
frontend-test-lint-code: &frontend-test-lint-code
|
||||
- '**/*'
|
||||
|
||||
frontend-test-unit-code: &frontend-test-unit-code
|
||||
- '**/*'
|
||||
|
||||
frontend-test-build-code: &frontend-test-build-code
|
||||
- '**/*'
|
||||
|
||||
frontend-test-build-docs: &frontend-test-build-docs
|
||||
- '**/*.md'
|
||||
- '.vuepress/*'
|
||||
|
||||
frontend-test-build-storybook: &frontend-test-build-storybook
|
||||
- '**/*'
|
||||
19
frontend/.github/workflows/frontend.deploy.chromatic.yml
vendored
Normal file
19
frontend/.github/workflows/frontend.deploy.chromatic.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: "frontend:deploy:chromatic"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
name: Chromatic - Frontend
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Frontend | Build
|
||||
run: npm install && npm run chromatic -- --exit-zero-on-changes
|
||||
21
frontend/.github/workflows/frontend.deploy.docs.yml
vendored
Normal file
21
frontend/.github/workflows/frontend.deploy.docs.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: "frontend:deploy:docs to github"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: vuepress-deploy
|
||||
uses: jenkey2011/vuepress-deploy@master
|
||||
env:
|
||||
ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
#TARGET_REPO: username/repo
|
||||
#TARGET_BRANCH: master
|
||||
BUILD_SCRIPT: npm install && npm run docs:build
|
||||
BUILD_DIR: build/docs/
|
||||
VUEPRESS_BASE: "boilerplate-frontend"
|
||||
34
frontend/.github/workflows/frontend.test.build.code.yml
vendored
Normal file
34
frontend/.github/workflows/frontend.test.build.code.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: "frontend:test:build test code"
|
||||
|
||||
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-code
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changes: ${{ steps.changes.outputs.frontend-test-build-code }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for frontend file changes
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/file-filters.yml
|
||||
list-files: shell
|
||||
|
||||
build:
|
||||
if: needs.files-changed.outputs.changes == 'true'
|
||||
name: Build - Frontend
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Frontend | Build
|
||||
run: npm install && npm run build
|
||||
34
frontend/.github/workflows/frontend.test.build.docs.yml
vendored
Normal file
34
frontend/.github/workflows/frontend.test.build.docs.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: "frontend:test:build test docs"
|
||||
|
||||
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-docs
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changes: ${{ steps.changes.outputs.frontend-test-build-docs }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for frontend file changes
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/file-filters.yml
|
||||
list-files: shell
|
||||
|
||||
build:
|
||||
if: needs.files-changed.outputs.changes == 'true'
|
||||
name: Build Docs - Frontend
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Frontend | Build Docs
|
||||
run: npm install && npm run docs:build
|
||||
34
frontend/.github/workflows/frontend.test.build.storybook.yml
vendored
Normal file
34
frontend/.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@v4
|
||||
|
||||
- name: Check for frontend file changes
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
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@v4
|
||||
|
||||
- name: Frontend | Build Storybook
|
||||
run: npm install && npm run storybook:build
|
||||
34
frontend/.github/workflows/frontend.test.lint.code.yml
vendored
Normal file
34
frontend/.github/workflows/frontend.test.lint.code.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: "frontend:test:lint code with defined linters"
|
||||
|
||||
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-lint-code
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changes: ${{ steps.changes.outputs.frontend-test-lint-code }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for frontend file changes
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/file-filters.yml
|
||||
list-files: shell
|
||||
|
||||
lint:
|
||||
if: needs.files-changed.outputs.changes == 'true'
|
||||
name: Lint - Frontend
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Frontend | Lint
|
||||
run: npm install && npm run test:lint
|
||||
34
frontend/.github/workflows/frontend.test.unit.code.yml
vendored
Normal file
34
frontend/.github/workflows/frontend.test.unit.code.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: "frontend:test:unit test code with defined suites"
|
||||
|
||||
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-unit-code
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changes: ${{ steps.changes.outputs.frontend-test-unit-code }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for frontend file changes
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/file-filters.yml
|
||||
list-files: shell
|
||||
|
||||
unit:
|
||||
if: needs.files-changed.outputs.changes == 'true'
|
||||
name: Unit - Frontend
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Frontend | Unit
|
||||
run: npm install && npm run test:unit
|
||||
77
frontend/.github/workflows/test.lint.pr.yml
vendored
Normal file
77
frontend/.github/workflows/test.lint.pr.yml
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
name: "test:lint pull request CI"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# Configure which types are allowed (newline delimited).
|
||||
# Default: https://github.com/commitizen/conventional-commit-types
|
||||
#types: |
|
||||
# fix
|
||||
# feat
|
||||
# Configure which scopes are allowed (newline delimited).
|
||||
# Append a scope for each service here
|
||||
scopes: |
|
||||
frontend
|
||||
docu
|
||||
docker
|
||||
release
|
||||
workflow
|
||||
other
|
||||
# Configure that a scope must always be provided.
|
||||
requireScope: true
|
||||
# Configure which scopes (newline delimited) are disallowed in PR
|
||||
# titles. For instance by setting # the value below, `chore(release):
|
||||
# ...` and `ci(e2e,release): ...` will be rejected.
|
||||
#disallowScopes: |
|
||||
# release
|
||||
# Configure additional validation for the subject based on a regex.
|
||||
# This example ensures the subject doesn't start with an uppercase character.
|
||||
subjectPattern: ^(?![A-Z]).+$
|
||||
# If `subjectPattern` is configured, you can use this property to override
|
||||
# the default error message that is shown when the pattern doesn't match.
|
||||
# The variables `subject` and `title` can be used within the message.
|
||||
subjectPatternError: |
|
||||
The subject "{subject}" found in the pull request title "{title}"
|
||||
didn't match the configured pattern. Please ensure that the subject
|
||||
doesn't start with an uppercase character.
|
||||
# If you use GitHub Enterprise, you can set this to the URL of your server
|
||||
#githubBaseUrl: https://github.myorg.com/api/v3
|
||||
# If the PR contains one of these labels (newline delimited), the
|
||||
# validation is skipped.
|
||||
# If you want to rerun the validation when labels change, you might want
|
||||
# to use the `labeled` and `unlabeled` event triggers in your workflow.
|
||||
#ignoreLabels: |
|
||||
# bot
|
||||
# ignore-semantic-pull-request
|
||||
# If you're using a format for the PR title that differs from the traditional Conventional
|
||||
# Commits spec, you can use these options to customize the parsing of the type, scope and
|
||||
# subject. The `headerPattern` should contain a regex where the capturing groups in parentheses
|
||||
# correspond to the parts listed in `headerPatternCorrespondence`.
|
||||
# See: https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-commits-parser#headerpattern
|
||||
headerPattern: '^(\w*)(?:\(([\w$.\-*/ ]*)\))?: (.*)$'
|
||||
headerPatternCorrespondence: type, scope, subject
|
||||
# For work-in-progress PRs you can typically use draft pull requests
|
||||
# from GitHub. However, private repositories on the free plan don't have
|
||||
# this option and therefore this action allows you to opt-in to using the
|
||||
# special "[WIP]" prefix to indicate this state. This will avoid the
|
||||
# validation of the PR title and the pull request checks remain pending.
|
||||
# Note that a second check will be reported if this is enabled.
|
||||
wip: true
|
||||
9
frontend/.gitignore
vendored
Normal file
9
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
build/
|
||||
coverage/
|
||||
!.storybook/
|
||||
!.vuepress/
|
||||
.vuepress/.temp/
|
||||
.vuepress/.cache/
|
||||
build-storybook.log
|
||||
.env
|
||||
14
frontend/.prettierrc.json
Normal file
14
frontend/.prettierrc.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"jsxSingleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
16
frontend/.remarkrc.json
Normal file
16
frontend/.remarkrc.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"plugins": [
|
||||
"remark-gfm",
|
||||
"remark-preset-lint-consistent",
|
||||
"remark-preset-lint-markdown-style-guide",
|
||||
"remark-preset-lint-recommended",
|
||||
[
|
||||
"remark-lint-maximum-line-length",
|
||||
false
|
||||
],
|
||||
[
|
||||
"remark-lint-list-item-indent",
|
||||
"space"
|
||||
]
|
||||
]
|
||||
}
|
||||
26
frontend/.storybook/StoryWrapper.vue
Normal file
26
frontend/.storybook/StoryWrapper.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<!-- .storybook/StoryWrapper.vue -->
|
||||
<template>
|
||||
<v-app :theme="themeName">
|
||||
<v-main>
|
||||
<slot name="story"></slot>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export const DEFAULT_THEME = 'light'
|
||||
export default {
|
||||
props: {
|
||||
themeName: {
|
||||
default: DEFAULT_THEME,
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.v-application .v-application__wrap {
|
||||
min-height: unset;
|
||||
}
|
||||
</style>
|
||||
22
frontend/.storybook/main.ts
Normal file
22
frontend/.storybook/main.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { StorybookConfig } from '@storybook/vue3-vite'
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/vue3-vite',
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: 'tag',
|
||||
},
|
||||
core: {
|
||||
disableTelemetry: true, // 👈 Disables telemetry
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
54
frontend/.storybook/preview.ts
Normal file
54
frontend/.storybook/preview.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { setup } from '@storybook/vue3'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import { setPageContext } from '#context/usePageContext'
|
||||
import i18n from '#plugins/i18n'
|
||||
import CreateVuetify from '#plugins/vuetify'
|
||||
|
||||
import { withVuetifyTheme } from './withVuetifyTheme.decorator'
|
||||
|
||||
import type { Preview } from '@storybook/vue3'
|
||||
|
||||
setup((app) => {
|
||||
// Registers your app's plugins into Storybook
|
||||
const pinia = createPinia()
|
||||
app.use(pinia)
|
||||
app.use(i18n)
|
||||
app.use(CreateVuetify(i18n))
|
||||
|
||||
setPageContext(app, { urlPathname: '' })
|
||||
})
|
||||
|
||||
export const decorators = [withVuetifyTheme]
|
||||
|
||||
export const globalTypes = {
|
||||
theme: {
|
||||
name: 'Theme',
|
||||
description: 'Global theme for components',
|
||||
defaultValue: 'light',
|
||||
toolbar: {
|
||||
icon: 'paintbrush',
|
||||
// Array of plain string values or MenuItem shape
|
||||
items: [
|
||||
{ value: 'light', title: 'Light', left: '🌞' },
|
||||
{ value: 'dark', title: 'Dark', left: '🌛' },
|
||||
],
|
||||
// Change title based on selected value
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default preview
|
||||
20
frontend/.storybook/withVuetifyTheme.decorator.ts
Normal file
20
frontend/.storybook/withVuetifyTheme.decorator.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { h } from 'vue'
|
||||
|
||||
import StoryWrapper, { DEFAULT_THEME } from './StoryWrapper.vue'
|
||||
|
||||
export const withVuetifyTheme = (storyFn, context) => {
|
||||
// Pull our global theme variable, fallback to DEFAULT_THEME
|
||||
const themeName = context.globals.theme || DEFAULT_THEME
|
||||
const story = storyFn()
|
||||
|
||||
return () => {
|
||||
return h(
|
||||
StoryWrapper,
|
||||
{ themeName }, // Props for StoryWrapper
|
||||
{
|
||||
// Puts your story into StoryWrapper's "story" slot with your story args
|
||||
story: () => h(story, { ...context.args }),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
9
frontend/.stylelintrc.json
Normal file
9
frontend/.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
frontend/.vuepress/config.ts
Normal file
9
frontend/.vuepress/config.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
|
||||
export default defineUserConfig({
|
||||
title: 'IT4C Frontend Boilerplate Documentation',
|
||||
description: 'IT4C Frontend Boilerplate Documentation',
|
||||
dest: 'build/docs',
|
||||
base: process.env.VUEPRESS_BASE ? `/${process.env.VUEPRESS_BASE}/` : '/',
|
||||
pagePatterns: ['**/*.md', '**/LICENSE', '!.vuepress', '!node_modules'],
|
||||
})
|
||||
122
frontend/Dockerfile
Normal file
122
frontend/Dockerfile
Normal file
@ -0,0 +1,122 @@
|
||||
FROM node:21-alpine3.17 as base
|
||||
|
||||
# ENVs (available in production aswell, can be overwritten by commandline or env file)
|
||||
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
|
||||
ENV DOCKER_WORKDIR="/app"
|
||||
## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0
|
||||
ENV BUILD_DATE="1970-01-01T00:00:00.00Z"
|
||||
## We cannot do $(npm run version).${BUILD_NUMBER} here so we default to 0.0.0.0
|
||||
ENV BUILD_VERSION="0.0.0.0"
|
||||
## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
|
||||
ENV BUILD_COMMIT="0000000"
|
||||
## SET NODE_ENV
|
||||
ENV NODE_ENV="production"
|
||||
## App relevant Envs
|
||||
ENV PORT="3002"
|
||||
|
||||
# Labels
|
||||
LABEL org.label-schema.build-date="${BUILD_DATE}"
|
||||
LABEL org.label-schema.name="it4c:frontend"
|
||||
LABEL org.label-schema.description="IT4C Frontend Boilerplate"
|
||||
LABEL org.label-schema.usage="https://github.com/IT4Change/boilerplate-frontend/blob/master/README.md"
|
||||
LABEL org.label-schema.url="https://github.com/IT4Change/boilerplate-frontend"
|
||||
LABEL org.label-schema.vcs-url="https://github.com/IT4Change/boilerplate-frontend/tree/master/"
|
||||
LABEL org.label-schema.vcs-ref="${BUILD_COMMIT}"
|
||||
LABEL org.label-schema.vendor="IT4C"
|
||||
LABEL org.label-schema.version="${BUILD_VERSION}"
|
||||
LABEL org.label-schema.schema-version="1.0"
|
||||
LABEL maintainer="info@it4c.dev"
|
||||
|
||||
# Install Additional Software
|
||||
## install: node-gyp dependencies
|
||||
# RUN apk --no-cache add g++ make python3
|
||||
|
||||
# Settings
|
||||
## Expose Container Port
|
||||
EXPOSE ${PORT}
|
||||
EXPOSE 24678
|
||||
|
||||
## Workdir
|
||||
RUN mkdir -p ${DOCKER_WORKDIR}
|
||||
WORKDIR ${DOCKER_WORKDIR}
|
||||
|
||||
##################################################################################
|
||||
# DEVELOPMENT (Connected to the local environment, to reload on demand) ##########
|
||||
##################################################################################
|
||||
FROM base as development
|
||||
|
||||
# We don't need to copy or build anything since we gonna bind to the
|
||||
# local filesystem which will need a rebuild anyway
|
||||
|
||||
# Run command
|
||||
# (for development we need to execute npm install since the
|
||||
# node_modules are on another volume and need updating)
|
||||
CMD /bin/sh -c "npm install && npm run dev"
|
||||
|
||||
##################################################################################
|
||||
# STORYBOOK ######################################################################
|
||||
##################################################################################
|
||||
FROM base as storybook
|
||||
|
||||
# We don't need to copy or build anything since we gonna bind to the
|
||||
# local filesystem which will need a rebuild anyway
|
||||
|
||||
# Run command
|
||||
# (for development we need to execute npm install since the
|
||||
# node_modules are on another volume and need updating)
|
||||
CMD /bin/sh -c "npm install && npm run storybook"
|
||||
|
||||
##################################################################################
|
||||
# DOCUMENTATION ##################################################################
|
||||
##################################################################################
|
||||
FROM base as documentation
|
||||
|
||||
# We don't need to copy or build anything since we gonna bind to the
|
||||
# local filesystem which will need a rebuild anyway
|
||||
|
||||
# Run command
|
||||
# (for development we need to execute npm install since the
|
||||
# node_modules are on another volume and need updating)
|
||||
CMD /bin/sh -c "npm install && npm run docs:dev"
|
||||
|
||||
##################################################################################
|
||||
# BUILD (Does contain all files and is therefore bloated) ########################
|
||||
##################################################################################
|
||||
FROM base as build
|
||||
|
||||
# Copy everything
|
||||
COPY . .
|
||||
# npm install
|
||||
RUN npm install --frozen-lockfile --non-interactive
|
||||
# npm build
|
||||
RUN npm run build
|
||||
|
||||
##################################################################################
|
||||
# TEST ###########################################################################
|
||||
##################################################################################
|
||||
#FROM build as test
|
||||
|
||||
# Install Additional Software
|
||||
# RUN apk add --no-cache bash jq
|
||||
|
||||
# Run command
|
||||
#CMD /bin/sh -c "yarn run dev"
|
||||
|
||||
##################################################################################
|
||||
# PRODUCTION (Does contain only "binary"- and static-files to reduce image size) #
|
||||
##################################################################################
|
||||
FROM base as production
|
||||
|
||||
# Copy "binary"-files from build image
|
||||
COPY --from=build ${DOCKER_WORKDIR}/build ./build
|
||||
# Copy server
|
||||
COPY --from=build ${DOCKER_WORKDIR}/server ./server
|
||||
# Copy package.json & tsconfig.json
|
||||
COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
|
||||
COPY --from=build ${DOCKER_WORKDIR}/package-lock.json ./package-lock.json
|
||||
COPY --from=build ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig.json
|
||||
# Install production packages
|
||||
RUN npm install --omit=dev --frozen-lockfile --non-interactive
|
||||
|
||||
# Run command
|
||||
CMD /bin/sh -c "npm run server:prod"
|
||||
201
frontend/LICENSE
Normal file
201
frontend/LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2023 Ulf Gebhardt
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
197
frontend/README.md
Normal file
197
frontend/README.md
Normal file
@ -0,0 +1,197 @@
|
||||
# boilerplate-frontend
|
||||
[![nodejs][badge-nodejs-img]][badge-nodejs-href]
|
||||
[![npm][badge-npm-img]][badge-npm-href]
|
||||
[![docker][badge-docker-img]][badge-docker-href]
|
||||
[![jq][badge-jq-img]][badge-jq-href]
|
||||
[![vue][badge-vue-img]][badge-vue-href]
|
||||
[![vike][badge-vike-img]][badge-vike-href]
|
||||
[![vuetify][badge-vuetify-img]][badge-vuetify-href]
|
||||
[![pinia][badge-pinia-img]][badge-pinia-href]
|
||||
[![vue-i18n][badge-vue-i18n-img]][badge-vue-i18n-href]
|
||||
[![eslint][badge-eslint-img]][badge-eslint-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]
|
||||
[![storybook][badge-storybook-img]][badge-storybook-href]
|
||||
[![vuepress][badge-vuepress-img]][badge-vuepress-href]
|
||||
[![chromatic][badge-chromatic-img]][badge-chromatic-href]
|
||||
|
||||
The IT4C Boilerplate for frontends
|
||||
|
||||

|
||||
|
||||
## Requirements & Technology
|
||||
|
||||
To be able to build this project you need `nodejs`, `npm` and optional `docker` and `jq`.
|
||||
|
||||
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`, `remark-cli` and `stylelint`.
|
||||
|
||||
This projects utilizes `storybook` and `chromatic` to develop, document & test frontend components and `vuepress` for static documentation generation.
|
||||
|
||||
## Commands
|
||||
|
||||
The following commands are available:
|
||||
|
||||
| Command | Description |
|
||||
|-----------------------------|--------------------------------------------------|
|
||||
| `npm install` | Project setup |
|
||||
| `npm run build` | Compiles and minifies for production |
|
||||
| `npm run server:prod` | Runs productions server |
|
||||
| **Develop** | |
|
||||
| `npm run dev` | Compiles and hot-reloads for development |
|
||||
| `npm run server:dev` | Run development server |
|
||||
| `npm run server:prod:ts` | Run production server without build (ts-node) |
|
||||
| `npm run server:build` | Build Server into an executable cjs file |
|
||||
| **Test** | |
|
||||
| `npm run test:lint` | Run all linters |
|
||||
| `npm run test:lint:eslint` | Run linter eslint |
|
||||
| `npm run test:lint:locales` | Run linter locales |
|
||||
| `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:update` | Run unit tests, coverage and update snapshots |
|
||||
| `npm run test:unit:dev` | Run all unit tests in watch mode |
|
||||
| `npm test` | Run all tests & linters |
|
||||
| **Storybook** | |
|
||||
| `npm run storybook` | Run Storybook |
|
||||
| `npm run storybook:build` | Build static storybook |
|
||||
| `npm run storybook:test` | Run tests against all storybook stories |
|
||||
| **Documentation** | |
|
||||
| `npm run docs:dev` | Run Documentation in development mode |
|
||||
| `npm run docs:build` | Build static documentation |
|
||||
| **Chromatic** | |
|
||||
| `npm run chromatic` | Run Chromatic. See Chromatic section for details |
|
||||
| **Maintenance** | |
|
||||
| `npm run update` | Check for updates |
|
||||
|
||||
### Docker
|
||||
|
||||
Docker can be run in development mode utilizing `docker-compose.overwrite.yml`:
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
||||
Docker can be run in production mode:
|
||||
```bash
|
||||
docker compose -f docker-compose.yml up
|
||||
```
|
||||
|
||||
### Chromatic
|
||||
|
||||
In order to use the chromatic workflow you need to provide a `CHROMATIC_PROJECT_TOKEN` in the repository secrets.
|
||||
|
||||
If you want to run chromatic from the command line you either have to provide this variable as well
|
||||
```bash
|
||||
export CHROMATIC_PROJECT_TOKEN=...
|
||||
npm run chromatic
|
||||
```
|
||||
or you have to append it via parameter:
|
||||
```bash
|
||||
npm run chromatic -- --project-token=...
|
||||
```
|
||||
|
||||
### Update
|
||||
|
||||
You can get a list of packes to update by running `npm run update`.
|
||||
|
||||
Appending `-u ` will also update the packages in the `package.json`. You have to run `npm install` again after.
|
||||
|
||||
```bash
|
||||
npm run update -- -u
|
||||
npm install
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
The following endpoints are provided given the right command is executed or all three if `docker compose` is used:
|
||||
|
||||
| Endpoint | Description |
|
||||
|------------------------------------------------|---------------|
|
||||
| [http://localhost:3000](http://localhost:3000) | Web |
|
||||
| [http://localhost:6006](http://localhost:6006) | Storybook |
|
||||
| [http://localhost:8080](http://localhost:8080) | Documentation |
|
||||
|
||||
## How to use as part of a project
|
||||
|
||||
If you want to use this as part of a larger project, e.g. in conjunction with a backend also utilizing a boilerplate you cannot use the template mechanic provided by github for this repository.
|
||||
|
||||
You can use the following commands to include the whole git history of the boilerplate and be able to update according to changes to this repo using another remote.
|
||||
|
||||
```bash
|
||||
git remote add xxx_boilerplate_frontend git@github.com:IT4Change/boilerplate-frontend.git
|
||||
git fetch xxx_boilerplate_frontend
|
||||
git merge -s ours --no-commit --allow-unrelated-histories xxx_boilerplate_frontend/master
|
||||
git read-tree --prefix=xxx/ -u xxx_boilerplate_frontend/master
|
||||
git commit -m "Imported boilerplate_frontend as a subtree under xxx/."
|
||||
```
|
||||
|
||||
To update the subtree you can use
|
||||
|
||||
```bash
|
||||
git subtree pull -P xxx/ xxx_boilerplate_frontend master
|
||||
git commit -m "Updated boilerplate_frontend in subtree under xxx/."
|
||||
```
|
||||
|
||||
Where `xxx` refers to the folder and product part you want to use the boilerplate in. This assumes that you might need several copies of the frontend boilerplate for you product.
|
||||
|
||||
This mechanic was taken from this [source](https://stackoverflow.com/questions/1683531/how-to-import-existing-git-repository-into-another/8396318#8396318)
|
||||
|
||||
## Known Problems
|
||||
|
||||
- [ ] [Image flicker](https://github.com/vuetifyjs/vuetify/issues/18772)
|
||||
- [ ] [Black Buttons](https://github.com/vuetifyjs/vuetify/issues/18773)
|
||||
|
||||
## License
|
||||
|
||||
[Apache 2.0](./LICENSE)
|
||||
|
||||
<!-- Badges -->
|
||||
[badge-nodejs-img]: https://img.shields.io/badge/nodejs-%3E%3D20.5.0-blue
|
||||
[badge-nodejs-href]: https://nodejs.org/
|
||||
|
||||
[badge-npm-img]: https://img.shields.io/badge/npm-latest-blue
|
||||
[badge-npm-href]: https://www.npmjs.com/package/npm
|
||||
|
||||
[badge-docker-img]: https://img.shields.io/badge/docker-latest-blue
|
||||
[badge-docker-href]: https://www.docker.com/
|
||||
|
||||
[badge-jq-img]: https://img.shields.io/badge/jq-latest-blue
|
||||
[badge-jq-href]: https://jqlang.github.io/jq/
|
||||
|
||||
[badge-vue-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=dependencies.vue&label=vue&color=green
|
||||
[badge-vue-href]: https://vuejs.org/
|
||||
|
||||
[badge-vike-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=dependencies.vike&label=vike&color=green
|
||||
[badge-vike-href]: https://vike.dev/
|
||||
|
||||
[badge-vuetify-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=dependencies.vuetify&label=vuetify&color=green
|
||||
[badge-vuetify-href]: https://vuetifyjs.com/
|
||||
|
||||
[badge-pinia-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=dependencies.pinia&label=pinia&color=green
|
||||
[badge-pinia-href]: https://pinia.vuejs.org/
|
||||
|
||||
[badge-vue-i18n-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=dependencies%5B%27vue-i18n%27%5D&label=vue-i18n&color=green
|
||||
[badge-vue-i18n-href]: https://vue-i18n.intlify.dev/
|
||||
|
||||
[badge-eslint-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies.eslint&label=eslint&color=yellow
|
||||
[badge-eslint-href]: https://eslint.org/
|
||||
|
||||
[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-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-href]: https://vitest.dev/
|
||||
|
||||
[badge-storybook-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies.storybook&label=storybook&color=orange
|
||||
[badge-storybook-href]: https://storybook.js.org/
|
||||
|
||||
[badge-vuepress-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies.vuepress&label=vuepress&color=orange
|
||||
[badge-vuepress-href]: https://vuepress.vuejs.org/
|
||||
|
||||
[badge-chromatic-img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FIT4Change%2Fboilerplate-frontend%2Fmaster%2Fpackage.json&query=devDependencies.chromatic&label=chromatic&color=orange
|
||||
[badge-chromatic-href]: https://www.chromatic.com/
|
||||
64
frontend/docker-compose.override.yml
Normal file
64
frontend/docker-compose.override.yml
Normal file
@ -0,0 +1,64 @@
|
||||
version: '3.4'
|
||||
services:
|
||||
# ######################################################
|
||||
# FRONTEND #############################################
|
||||
# ######################################################
|
||||
frontend:
|
||||
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||
image: it4c/frontend:local-development
|
||||
build:
|
||||
target: development
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
# - DEBUG=true
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- frontend_node_modules:/app/node_modules
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./:/app
|
||||
|
||||
# ######################################################
|
||||
# STORYBOOK ############################################
|
||||
# ######################################################
|
||||
storybook:
|
||||
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||
image: it4c/frontend:local-storybook
|
||||
build:
|
||||
target: storybook
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
# - DEBUG=true
|
||||
ports:
|
||||
- 6006:6006
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- storybook_node_modules:/app/node_modules
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./:/app
|
||||
|
||||
# ######################################################
|
||||
# DOCUMENTATION ########################################
|
||||
# ######################################################
|
||||
documentation:
|
||||
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||
image: it4c/frontend:local-documentation
|
||||
build:
|
||||
target: documentation
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
# - DEBUG=true
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- documentation_node_modules:/app/node_modules
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./:/app
|
||||
|
||||
volumes:
|
||||
frontend_node_modules:
|
||||
storybook_node_modules:
|
||||
documentation_node_modules:
|
||||
33
frontend/docker-compose.yml
Normal file
33
frontend/docker-compose.yml
Normal file
@ -0,0 +1,33 @@
|
||||
# This file defines the production settings. It is overwritten by docker-compose.override.yml,
|
||||
# which defines the development settings. The override.yml is loaded by default. Therefore it
|
||||
# is required to explicitly define if you want an production build:
|
||||
# > docker-compose -f docker-compose.yml up
|
||||
version: '3.4'
|
||||
services:
|
||||
frontend:
|
||||
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||
image: it4c/frontend:local-production
|
||||
build:
|
||||
context: .
|
||||
target: production
|
||||
networks:
|
||||
- external-net
|
||||
- internal-net
|
||||
ports:
|
||||
- 3000:3000
|
||||
environment:
|
||||
# Envs used in Dockerfile
|
||||
# - DOCKER_WORKDIR="/app"
|
||||
# - PORT=3000
|
||||
# - BUILD_DATE="1970-01-01T00:00:00.00Z"
|
||||
# - BUILD_VERSION="0.0.0.0"
|
||||
# - BUILD_COMMIT="0000000"
|
||||
- NODE_ENV="production"
|
||||
# env_file:
|
||||
# - ./.env
|
||||
# - ./frontend/.env
|
||||
|
||||
networks:
|
||||
external-net:
|
||||
internal-net:
|
||||
internal: true
|
||||
35830
frontend/package-lock.json
generated
Normal file
35830
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
155
frontend/package.json
Normal file
155
frontend/package.json
Normal file
@ -0,0 +1,155 @@
|
||||
{
|
||||
"name": "boilerplate-frontend",
|
||||
"version": "1.0.0",
|
||||
"description": "The IT4C Boilerplate for frontends",
|
||||
"main": "build/index.js",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/IT4Change/boilerplate-frontend.git"
|
||||
},
|
||||
"keywords": [
|
||||
"nodejs",
|
||||
"npm",
|
||||
"docker",
|
||||
"jq",
|
||||
"vue",
|
||||
"vike",
|
||||
"vuetify",
|
||||
"pinia",
|
||||
"vue-i18n",
|
||||
"eslint",
|
||||
"remark-cli",
|
||||
"stylelint",
|
||||
"vitest",
|
||||
"storybook",
|
||||
"vuepress",
|
||||
"chromatic"
|
||||
],
|
||||
"author": {
|
||||
"name": "Ulf Gebhardt"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/IT4Change/boilerplate-frontend/issues"
|
||||
},
|
||||
"homepage": "https://github.com/IT4Change/boilerplate-frontend#readme",
|
||||
"scripts": {
|
||||
"dev": "npm run server:dev",
|
||||
"prod": "npm run build && npm run server:prod",
|
||||
"build": "vite build && npm run server:build",
|
||||
"server": "node --loader ts-node/esm ./server/index.ts",
|
||||
"server:dev": "npm run server",
|
||||
"server:prod": "node ./build/index.cjs",
|
||||
"server:prod:ts": "cross-env NODE_ENV=production npm run server",
|
||||
"server:build": "tsx scripts/buildServer/buildServer",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:build": "storybook build -o build/storybook",
|
||||
"storybook:test": "test-storybook",
|
||||
"test:lint": "npm run test:lint:eslint && npm run test:lint:remark && npm run test:lint:style && npm run test:lint:locales",
|
||||
"test:lint:eslint": "eslint --ext .vue,.ts,.tsx,.js,.jsx,.cjs,.mjs,.json,.yml,.yaml --max-warnings 0 .",
|
||||
"test:lint:locales": "scripts/locales/locales.sh src/locales",
|
||||
"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:update": "npm run test:unit:dev -- run --coverage -u",
|
||||
"test:unit:dev": "vitest",
|
||||
"test": "npm run test:lint && npm run test:unit",
|
||||
"docs:dev": "vuepress dev .",
|
||||
"docs:build": "vuepress build .",
|
||||
"chromatic": "npx chromatic --build-script-name storybook:build",
|
||||
"update": "npx npm-check-updates"
|
||||
},
|
||||
"dependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^2.0.0",
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.11.8",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@vue/compiler-sfc": "^3.4.15",
|
||||
"@vue/server-renderer": "^3.4.15",
|
||||
"compression": "^1.7.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"express": "^4.18.2",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"sass": "^1.70.0",
|
||||
"sass-loader": "^14.0.0",
|
||||
"sirv": "^2.0.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3",
|
||||
"vike": "^0.4.160",
|
||||
"vite": "^5.0.12",
|
||||
"vue": "^3.4.15",
|
||||
"vue-i18n": "^9.9.0",
|
||||
"vuetify": "^3.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.1.0",
|
||||
"@intlify/eslint-plugin-vue-i18n": "^2.0.0",
|
||||
"@storybook/addon-essentials": "^7.6.10",
|
||||
"@storybook/addon-interactions": "^7.6.10",
|
||||
"@storybook/addon-links": "^7.6.10",
|
||||
"@storybook/blocks": "^7.6.10",
|
||||
"@storybook/test-runner": "^0.16.0",
|
||||
"@storybook/testing-library": "^0.2.2",
|
||||
"@storybook/vue3": "^7.6.10",
|
||||
"@storybook/vue3-vite": "^7.6.10",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
||||
"@typescript-eslint/parser": "^6.19.1",
|
||||
"@vitest/coverage-v8": "^1.2.2",
|
||||
"@vue/test-utils": "^2.4.4",
|
||||
"@vuepress/bundler-vite": "^2.0.0-rc.2",
|
||||
"chromatic": "^10.6.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-json": "^3.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-security": "^2.1.0",
|
||||
"eslint-plugin-storybook": "^0.6.15",
|
||||
"eslint-plugin-vitest": "^0.3.20",
|
||||
"eslint-plugin-vue": "^9.20.1",
|
||||
"eslint-plugin-vuetify": "^2.1.1",
|
||||
"eslint-plugin-yml": "^1.12.2",
|
||||
"happy-dom": "^13.3.1",
|
||||
"prettier": "^3.2.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"remark-cli": "^12.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-preset-lint-consistent": "^5.1.2",
|
||||
"remark-preset-lint-markdown-style-guide": "^5.1.3",
|
||||
"remark-preset-lint-recommended": "^6.1.3",
|
||||
"storybook": "^7.6.10",
|
||||
"stylelint": "^16.2.0",
|
||||
"stylelint-config-css-modules": "^4.4.0",
|
||||
"stylelint-config-recess-order": "^4.4.0",
|
||||
"stylelint-config-recommended-vue": "^1.5.0",
|
||||
"stylelint-config-standard": "^36.0.0",
|
||||
"stylelint-config-standard-scss": "^13.0.0",
|
||||
"tsx": "^4.7.0",
|
||||
"vite-plugin-checker": "^0.6.2",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-vuetify": "^2.0.1",
|
||||
"vitest": "^1.2.2",
|
||||
"vue-tsc": "^1.8.27",
|
||||
"vuepress": "^2.0.0-rc.0"
|
||||
},
|
||||
"imports": {
|
||||
"#components/*": "./src/components/*",
|
||||
"#pages/*": "./src/pages/*",
|
||||
"#assets/*": "./src/assets/*",
|
||||
"#layouts/*": "./src/layouts/*",
|
||||
"#stores/*": "./src/stores/*",
|
||||
"#src/*": "./src/*",
|
||||
"#plugins/*": "./renderer/plugins/*",
|
||||
"#context/*": "./renderer/context/*",
|
||||
"#types/*": "./types/*",
|
||||
"#root/*": "./*"
|
||||
}
|
||||
}
|
||||
16
frontend/renderer/+config.h.ts
Normal file
16
frontend/renderer/+config.h.ts
Normal file
@ -0,0 +1,16 @@
|
||||
// See https://vike.dev/data-fetching
|
||||
export default {
|
||||
clientRouting: true,
|
||||
prefetchStaticAssets: 'viewport',
|
||||
passToClient: ['pageProps', /* 'urlPathname', */ 'routeParams'],
|
||||
meta: {
|
||||
title: {
|
||||
// Make the value of `title` available on both the server- and client-side
|
||||
env: { server: true, client: true },
|
||||
},
|
||||
description: {
|
||||
// Make the value of `description` available only on the server-side
|
||||
env: { server: true },
|
||||
},
|
||||
},
|
||||
}
|
||||
18
frontend/renderer/+onRenderClient.ts
Normal file
18
frontend/renderer/+onRenderClient.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { PageContext } from 'vike/types'
|
||||
|
||||
import { createApp } from './app'
|
||||
import { getTitle } from './utils'
|
||||
|
||||
let instance: ReturnType<typeof createApp>
|
||||
/* async */ function render(pageContext: PageContext) {
|
||||
if (!instance) {
|
||||
instance = createApp(pageContext)
|
||||
instance.app.mount('#app')
|
||||
} else {
|
||||
instance.app.changePage(pageContext)
|
||||
}
|
||||
|
||||
document.title = getTitle(pageContext)
|
||||
}
|
||||
|
||||
export default render
|
||||
74
frontend/renderer/+onRenderHtml.ts
Normal file
74
frontend/renderer/+onRenderHtml.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { renderToString as renderToString_ } from '@vue/server-renderer'
|
||||
import { escapeInject, dangerouslySkipEscape } from 'vike/server'
|
||||
import { PageContext, PageContextServer } from 'vike/types'
|
||||
|
||||
import logoUrl from '#assets/favicon.ico'
|
||||
import image from '#assets/it4c-logo2-clean-bg_alpha-128x128.png'
|
||||
import { META } from '#src/env'
|
||||
|
||||
import { createApp } from './app'
|
||||
import { getDescription, getTitle } from './utils'
|
||||
|
||||
import type { App } from 'vue'
|
||||
|
||||
async function render(pageContext: PageContextServer & PageContext) {
|
||||
const { app, i18n } = createApp(pageContext, false)
|
||||
|
||||
const locale = i18n.global.locale.value
|
||||
|
||||
const appHtml = await renderToString(app)
|
||||
|
||||
// See https://vike.dev/head
|
||||
const title = getTitle(pageContext)
|
||||
const description = getDescription(pageContext)
|
||||
|
||||
const documentHtml = escapeInject`<!DOCTYPE html>
|
||||
<html lang="${locale}">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="${logoUrl}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="${description}" />
|
||||
<meta name="author" content="${META.DEFAULT_AUTHOR}">
|
||||
<meta property="og:title" content="${title}" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="${META.BASE_URL}" />
|
||||
<meta property="og:description" content="${description}" />
|
||||
<meta property="og:image" content="${META.BASE_URL}${image}" />
|
||||
<meta property="og:image:alt" content="${title}" />
|
||||
<meta property="og:image:width" content="1200"/>
|
||||
<meta property="og:image:height" content="601"/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<!--<meta name="twitter:site" content="@YourTwitterUsername" />-->
|
||||
<meta name="twitter:title" content="${title}" />
|
||||
<meta name="twitter:text:title" content="${title}" />
|
||||
<meta name="twitter:description" content="${description}" />
|
||||
<meta name="twitter:image" content="${META.BASE_URL}${image}" />
|
||||
<meta name="twitter:image:alt" content="${title}" />
|
||||
<title>${title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">${dangerouslySkipEscape(appHtml)}</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
return {
|
||||
documentHtml,
|
||||
pageContext: {
|
||||
// We can add some `pageContext` here, which is useful if we want to do page redirection https://vike.dev/page-redirection
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function renderToString(app: App) {
|
||||
let err: unknown
|
||||
// Workaround: renderToString_() swallows errors in production, see https://github.com/vuejs/core/issues/7876
|
||||
app.config.errorHandler = (err_) => {
|
||||
err = err_
|
||||
}
|
||||
const appHtml = await renderToString_(app)
|
||||
if (err) throw err
|
||||
return appHtml
|
||||
}
|
||||
|
||||
export default render
|
||||
71
frontend/renderer/app.ts
Normal file
71
frontend/renderer/app.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import { PageContext } from 'vike/types'
|
||||
import { createSSRApp, defineComponent, h, markRaw, reactive, Component } from 'vue'
|
||||
|
||||
import PageShell from '#components/PageShell.vue'
|
||||
import { setPageContext } from '#context/usePageContext'
|
||||
import i18n from '#plugins/i18n'
|
||||
import pinia from '#plugins/pinia'
|
||||
import CreateVuetify from '#plugins/vuetify'
|
||||
|
||||
const vuetify = CreateVuetify(i18n)
|
||||
|
||||
function createApp(pageContext: PageContext, isClient = true) {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
let rootComponent: InstanceType<typeof PageWithWrapper>
|
||||
const PageWithWrapper = defineComponent({
|
||||
data: () => ({
|
||||
Page: markRaw(pageContext.Page),
|
||||
pageProps: markRaw(pageContext.pageProps || {}),
|
||||
isClient,
|
||||
}),
|
||||
created() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
rootComponent = this
|
||||
},
|
||||
render() {
|
||||
return h(
|
||||
PageShell as Component,
|
||||
{},
|
||||
{
|
||||
default: () => {
|
||||
return h(this.Page, this.pageProps)
|
||||
},
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
if (isClient) {
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
}
|
||||
|
||||
const app = createSSRApp(PageWithWrapper)
|
||||
app.use(pinia)
|
||||
app.use(i18n)
|
||||
app.use(vuetify)
|
||||
|
||||
objectAssign(app, {
|
||||
changePage: (pageContext: PageContext) => {
|
||||
Object.assign(pageContextReactive, pageContext)
|
||||
rootComponent.Page = markRaw(pageContext.Page)
|
||||
rootComponent.pageProps = markRaw(pageContext.pageProps || {})
|
||||
},
|
||||
})
|
||||
|
||||
const pageContextReactive = reactive(pageContext)
|
||||
|
||||
setPageContext(app, pageContextReactive)
|
||||
|
||||
return { app, i18n }
|
||||
}
|
||||
|
||||
// Same as `Object.assign()` but with type inference
|
||||
function objectAssign<Obj extends object, ObjAddendum>(
|
||||
obj: Obj,
|
||||
objAddendum: ObjAddendum,
|
||||
): asserts obj is Obj & ObjAddendum {
|
||||
Object.assign(obj, objAddendum)
|
||||
}
|
||||
|
||||
export { createApp }
|
||||
22
frontend/renderer/context/usePageContext.ts
Normal file
22
frontend/renderer/context/usePageContext.ts
Normal file
@ -0,0 +1,22 @@
|
||||
// `usePageContext` allows us to access `pageContext` in any Vue component.
|
||||
// See https://vike.dev/pageContext-anywhere
|
||||
|
||||
import { PageContext } from 'vike/types'
|
||||
import { inject } from 'vue'
|
||||
|
||||
import type { App, InjectionKey } from 'vue'
|
||||
|
||||
export const vikePageContext: InjectionKey<PageContext> = Symbol('pageContext')
|
||||
|
||||
function usePageContext() {
|
||||
const pageContext = inject(vikePageContext)
|
||||
if (!pageContext) throw new Error('setPageContext() not called in parent')
|
||||
return pageContext
|
||||
}
|
||||
|
||||
function setPageContext(app: App, pageContext: PageContext) {
|
||||
app.provide(vikePageContext, pageContext)
|
||||
}
|
||||
|
||||
export { usePageContext }
|
||||
export { setPageContext }
|
||||
14
frontend/renderer/plugins/i18n.ts
Normal file
14
frontend/renderer/plugins/i18n.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import de from '#src/locales/de.json'
|
||||
// import { de as $vuetify } from 'vuetify/locale'
|
||||
import en from '#src/locales/en.json'
|
||||
// import { en as $vuetify } from 'vuetify/locale'
|
||||
|
||||
export default createI18n({
|
||||
legacy: false, // Vuetify does not support the legacy mode of vue-i18n
|
||||
globalInjection: true,
|
||||
locale: 'de',
|
||||
fallbackLocale: 'en',
|
||||
messages: { de, en },
|
||||
})
|
||||
4
frontend/renderer/plugins/pinia.ts
Normal file
4
frontend/renderer/plugins/pinia.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
const pinia = createPinia()
|
||||
export default pinia
|
||||
16
frontend/renderer/plugins/vuetify.ts
Normal file
16
frontend/renderer/plugins/vuetify.ts
Normal file
@ -0,0 +1,16 @@
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import 'vuetify/styles'
|
||||
import { I18n, useI18n } from 'vue-i18n'
|
||||
import { createVuetify } from 'vuetify'
|
||||
import { createVueI18nAdapter } from 'vuetify/locale/adapters/vue-i18n'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export default (i18n: I18n<any, NonNullable<unknown>, NonNullable<unknown>, string, false>) =>
|
||||
createVuetify({
|
||||
locale: {
|
||||
adapter: createVueI18nAdapter({ i18n, useI18n }),
|
||||
},
|
||||
ssr: true,
|
||||
})
|
||||
19
frontend/renderer/utils.ts
Normal file
19
frontend/renderer/utils.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { PageContext } from 'vike/types'
|
||||
|
||||
import { META } from '#src/env'
|
||||
|
||||
function getTitle(pageContext: PageContext) {
|
||||
// The value exported by /pages/**/+title.js is available at pageContext.config.title
|
||||
const val = pageContext.config.title
|
||||
if (typeof val === 'string') return val
|
||||
if (typeof val === 'function') return String(val(pageContext))
|
||||
return META.DEFAULT_TITLE
|
||||
}
|
||||
function getDescription(pageContext: PageContext) {
|
||||
const val = pageContext.config.description
|
||||
if (typeof val === 'string') return val
|
||||
if (typeof val === 'function') return val(pageContext)
|
||||
return META.DEFAULT_DESCRIPTION
|
||||
}
|
||||
|
||||
export { getTitle, getDescription }
|
||||
56
frontend/scripts/buildServer/buildServer.ts
Normal file
56
frontend/scripts/buildServer/buildServer.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { build } from 'esbuild'
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import fs, { ensureDir, remove } from 'fs-extra'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
async function buildServer() {
|
||||
const result = await build({
|
||||
absWorkingDir: process.cwd(),
|
||||
entryPoints: [path.join(path.resolve(__dirname, '../../server/'), 'index.ts')],
|
||||
outfile: 'index.cjs',
|
||||
write: false,
|
||||
minify: true,
|
||||
platform: 'node',
|
||||
bundle: true,
|
||||
format: 'cjs',
|
||||
sourcemap: false,
|
||||
treeShaking: true,
|
||||
define: { 'import.meta.url': 'importMetaUrl', 'process.env.NODE_ENV': '"production"' },
|
||||
inject: [path.resolve(__dirname, './import.meta.url-polyfill.ts')],
|
||||
banner: {
|
||||
js: `/* eslint-disable prettier/prettier */`,
|
||||
},
|
||||
tsconfig: path.resolve(__dirname, './tsconfig.buildServer.json'),
|
||||
plugins: [
|
||||
{
|
||||
name: 'externalize-deps',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /.*/ }, (args) => {
|
||||
const id = args.path
|
||||
if (id[0] !== '.' && !path.isAbsolute(id)) {
|
||||
return {
|
||||
external: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
const { text } = result.outputFiles[0]
|
||||
const filePath = path.join(path.resolve(__dirname, '../../build/'), 'index.cjs')
|
||||
if (fs.existsSync(filePath)) {
|
||||
await remove(filePath)
|
||||
}
|
||||
await ensureDir(path.dirname(filePath))
|
||||
// eslint-disable-next-line import/no-named-as-default-member
|
||||
await fs.writeFile(filePath, text)
|
||||
}
|
||||
|
||||
void buildServer()
|
||||
7
frontend/scripts/buildServer/import.meta.url-polyfill.ts
Normal file
7
frontend/scripts/buildServer/import.meta.url-polyfill.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
export const importMetaUrl =
|
||||
typeof document === 'undefined'
|
||||
? (new (require('url').URL)('file:' + __filename) as URL).href
|
||||
: (document.currentScript && (document.currentScript as any).src) ||
|
||||
new URL('main.js', document.baseURI).href
|
||||
16
frontend/scripts/buildServer/tsconfig.buildServer.json
Normal file
16
frontend/scripts/buildServer/tsconfig.buildServer.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"isolatedModules": true,
|
||||
"lib": ["esnext", "dom", "DOM.Iterable"],
|
||||
"strict": false,
|
||||
"sourceMap": false,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"declaration": false
|
||||
}
|
||||
}
|
||||
1
frontend/scripts/locales/keys.jq
Normal file
1
frontend/scripts/locales/keys.jq
Normal file
@ -0,0 +1 @@
|
||||
path(..)|[.[]|tostring]|join(".")
|
||||
44
frontend/scripts/locales/locales.sh
Executable file
44
frontend/scripts/locales/locales.sh
Executable file
@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
if [ $# -eq 0 ]
|
||||
then
|
||||
echo "You have to supply at least one argument specifying the folder to lint"
|
||||
fi
|
||||
|
||||
FILES="$1"
|
||||
|
||||
tmp=$(mktemp)
|
||||
exit_code=0
|
||||
|
||||
for locale_file in $FILES/*.json
|
||||
do
|
||||
jq -f $(dirname "$0")/sort.jq $locale_file > "$tmp"
|
||||
# check sort order and fix it if required
|
||||
if [ "$2" == "--fix" ]
|
||||
then
|
||||
mv "$tmp" $locale_file
|
||||
else
|
||||
if diff -q "$tmp" $locale_file > /dev/null ;
|
||||
then
|
||||
: # all good
|
||||
else
|
||||
exit_code=$?
|
||||
echo "$(basename -- $locale_file) is not sorted by keys"
|
||||
fi
|
||||
fi
|
||||
# check keys
|
||||
if [ -n "$LAST_FILE" ]; then
|
||||
listPaths="jq -f $(dirname "$0")/keys.jq"
|
||||
diffString="<( cat $LAST_FILE | $listPaths ) <( cat $locale_file | $listPaths )"
|
||||
if eval "diff -q $diffString";
|
||||
then
|
||||
: # all good
|
||||
else
|
||||
eval "diff -y $diffString | grep '[|<>]'";
|
||||
printf "\n$LAST_FILE\" and $locale_file translation keys do not match, see diff above.\n"
|
||||
exit_code=1
|
||||
fi
|
||||
fi
|
||||
LAST_FILE=$locale_file
|
||||
done
|
||||
|
||||
exit $exit_code
|
||||
13
frontend/scripts/locales/sort.jq
Normal file
13
frontend/scripts/locales/sort.jq
Normal file
@ -0,0 +1,13 @@
|
||||
def walk(f):
|
||||
. as $in
|
||||
| if type == "object" then
|
||||
reduce keys_unsorted[] as $key
|
||||
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
|
||||
elif type == "array" then map( walk(f) ) | f
|
||||
else f
|
||||
end;
|
||||
|
||||
def keys_sort_by(f):
|
||||
to_entries | sort_by(.key|f ) | from_entries;
|
||||
|
||||
walk(if type == "object" then keys_sort_by(ascii_upcase) else . end)
|
||||
6
frontend/scripts/tests/mock.$t.ts
Normal file
6
frontend/scripts/tests/mock.$t.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { config } from '@vue/test-utils'
|
||||
|
||||
config.global.mocks = {
|
||||
...config.global.mocks,
|
||||
$t: (tKey: string) => "$t('" + tKey + "')", // just return translation key
|
||||
}
|
||||
8
frontend/scripts/tests/mock.vikePageContext.ts
Normal file
8
frontend/scripts/tests/mock.vikePageContext.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { config } from '@vue/test-utils'
|
||||
|
||||
import { vikePageContext } from '#context/usePageContext'
|
||||
|
||||
config.global.provide = {
|
||||
...config.global.provide,
|
||||
[vikePageContext as symbol]: { urlPathname: '/some-url' },
|
||||
}
|
||||
14
frontend/scripts/tests/plugin.i18n-vuetify.ts
Normal file
14
frontend/scripts/tests/plugin.i18n-vuetify.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { config } from '@vue/test-utils'
|
||||
|
||||
import i18n from '#plugins/i18n'
|
||||
import vuetify from '#plugins/vuetify'
|
||||
|
||||
config.global.plugins.push(i18n)
|
||||
config.global.plugins.push(vuetify(i18n))
|
||||
|
||||
config.global.mocks = {
|
||||
...config.global.mocks,
|
||||
i18n$t: i18n.global.t,
|
||||
i18n$d: i18n.global.d,
|
||||
i18n$n: i18n.global.n,
|
||||
}
|
||||
5
frontend/scripts/tests/plugin.pinia.ts
Normal file
5
frontend/scripts/tests/plugin.pinia.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { config } from '@vue/test-utils'
|
||||
|
||||
import pinia from '#plugins/pinia'
|
||||
|
||||
config.global.plugins.push(pinia)
|
||||
97
frontend/server/index.ts
Normal file
97
frontend/server/index.ts
Normal file
@ -0,0 +1,97 @@
|
||||
// This file isn't processed by Vite, see https://github.com/vikejs/vike/issues/562
|
||||
// Consequently:
|
||||
// - When changing this file, you needed to manually restart your server for your changes to take effect.
|
||||
// - To use your environment variables defined in your .env files, you need to install dotenv, see https://vike.dev/env
|
||||
// - To use your path aliases defined in your vite.config.js, you need to tell Node.js about them, see https://vike.dev/path-aliases
|
||||
|
||||
// If you want Vite to process your server code then use one of these:
|
||||
// - vavite (https://github.com/cyco130/vavite)
|
||||
// - See vavite + Vike examples at https://github.com/cyco130/vavite/tree/main/examples
|
||||
// - vite-node (https://github.com/antfu/vite-node)
|
||||
// - HatTip (https://github.com/hattipjs/hattip)
|
||||
// - You can use Bati (https://batijs.github.io/) to scaffold a Vike + HatTip app. Note that Bati generates apps that use the V1 design (https://vike.dev/migration/v1-design) and Vike packages (https://vike.dev/vike-packages)
|
||||
|
||||
import compression from 'compression'
|
||||
import express from 'express'
|
||||
import { renderPage } from 'vike/server'
|
||||
|
||||
import { root } from './root.js'
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
|
||||
void startServer()
|
||||
|
||||
async function startServer() {
|
||||
const app = express()
|
||||
|
||||
// Vite integration
|
||||
if (isProduction) {
|
||||
// In production, we need to serve our static assets ourselves.
|
||||
// (In dev, Vite's middleware serves our static assets.)
|
||||
const sirv = (await import('sirv')).default
|
||||
// assets 1y caching
|
||||
app.use(
|
||||
'/assets',
|
||||
sirv(`${root}/build/client/assets`, {
|
||||
maxAge: 31536000, // 1Y
|
||||
immutable: true,
|
||||
gzip: true,
|
||||
}),
|
||||
)
|
||||
// cache things for 10min
|
||||
app.use(
|
||||
sirv(`${root}/build/client`, {
|
||||
maxAge: 600,
|
||||
immutable: true,
|
||||
gzip: true,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
// We instantiate Vite's development server and integrate its middleware to our server.
|
||||
// ⚠️ We instantiate it only in development. (It isn't needed in production and it
|
||||
// would unnecessarily bloat our production server.)
|
||||
const vite = await import('vite')
|
||||
const viteDevMiddleware = (
|
||||
await vite.createServer({
|
||||
root,
|
||||
server: { middlewareMode: true },
|
||||
})
|
||||
).middlewares
|
||||
app.use(viteDevMiddleware)
|
||||
|
||||
// on the fly compression
|
||||
app.use(compression())
|
||||
}
|
||||
|
||||
// ...
|
||||
// Other middlewares (e.g. some RPC middleware such as Telefunc)
|
||||
// ...
|
||||
|
||||
// Vike middleware. It should always be our last middleware (because it's a
|
||||
// catch-all middleware superseding any middleware placed after it).
|
||||
app.get('*', (req, res, next) => {
|
||||
void (async (req, res, next) => {
|
||||
const pageContextInit = {
|
||||
urlOriginal: req.originalUrl,
|
||||
}
|
||||
const pageContext = await renderPage(pageContextInit)
|
||||
const { httpResponse } = pageContext
|
||||
if (!httpResponse) {
|
||||
next()
|
||||
} else {
|
||||
const { body, statusCode, headers, earlyHints } = httpResponse
|
||||
if (res.writeEarlyHints)
|
||||
res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) })
|
||||
headers.forEach(([name, value]) => res.setHeader(name, value))
|
||||
res.status(statusCode)
|
||||
// For HTTP streams use httpResponse.pipe() instead, see https://vike.dev/stream
|
||||
res.send(body)
|
||||
}
|
||||
})(req, res, next)
|
||||
})
|
||||
|
||||
const port = process.env.PORT || 3000
|
||||
app.listen(port)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`🚀 Server running at http://localhost:${port}`)
|
||||
}
|
||||
8
frontend/server/root.ts
Normal file
8
frontend/server/root.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-when-using-the-experimental-modules-flag/50052194#50052194
|
||||
|
||||
import { dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const root = `${__dirname}/..`
|
||||
export { root }
|
||||
9
frontend/server/tsconfig.json
Normal file
9
frontend/server/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
// Make IDEs complain about missing file extension .js in import paths.
|
||||
// Alternatively, we could always set "module" to "Node16" and add the file extension .js to import paths everywhere.
|
||||
"compilerOptions": {
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16"
|
||||
}
|
||||
}
|
||||
BIN
frontend/src/assets/favicon.ico
Normal file
BIN
frontend/src/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
frontend/src/assets/it4c-logo2-clean-bg_alpha-128x128.png
Normal file
BIN
frontend/src/assets/it4c-logo2-clean-bg_alpha-128x128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
1
frontend/src/assets/sass/style.scss
Normal file
1
frontend/src/assets/sass/style.scss
Normal file
@ -0,0 +1 @@
|
||||
/* stylelint-disable no-empty-source */
|
||||
12
frontend/src/components/ClientOnly.test.ts
Normal file
12
frontend/src/components/ClientOnly.test.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
import ClientOnly from './ClientOnly.vue'
|
||||
|
||||
describe('ClientOnly', () => {
|
||||
const wrapper = mount(ClientOnly)
|
||||
|
||||
it('renders content if mounted', () => {
|
||||
expect(wrapper.isVisible()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
13
frontend/src/components/ClientOnly.vue
Normal file
13
frontend/src/components/ClientOnly.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<template v-if="isMounted"><slot /></template>
|
||||
<template v-else><slot name="placeholder" /></template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const isMounted = ref(false)
|
||||
onMounted(() => {
|
||||
isMounted.value = true
|
||||
})
|
||||
</script>
|
||||
16
frontend/src/components/PageShell.test.ts
Normal file
16
frontend/src/components/PageShell.test.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
import PageShell from './PageShell.vue'
|
||||
|
||||
describe('PageShell', () => {
|
||||
const wrapper = mount(PageShell, {
|
||||
slots: {
|
||||
default: 'Page Content',
|
||||
},
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.element).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
10
frontend/src/components/PageShell.vue
Normal file
10
frontend/src/components/PageShell.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<TopMenu />
|
||||
<slot />
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TopMenu from '#components/menu/TopMenu.vue'
|
||||
</script>
|
||||
56
frontend/src/components/VikeBtn.test.ts
Normal file
56
frontend/src/components/VikeBtn.test.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { navigate } from 'vike/client/router'
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
|
||||
import VikeBtn from './VikeBtn.vue'
|
||||
|
||||
vi.mock('vike/client/router')
|
||||
vi.mocked(navigate).mockResolvedValue()
|
||||
|
||||
describe('VikeBtn', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(VikeBtn, {
|
||||
attrs: { href: '/some-path' },
|
||||
})
|
||||
}
|
||||
let wrapper: ReturnType<typeof Wrapper>
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.element).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('icon is hidden', () => {
|
||||
expect(wrapper.find('.v-icon').exists()).toBe(false)
|
||||
})
|
||||
|
||||
describe('with href attribute app', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setProps({ href: '/app' } as Partial<object>)
|
||||
})
|
||||
|
||||
it('has flat variant', () => {
|
||||
expect(wrapper.classes()).toContain('v-btn--variant-flat')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with same href attribute', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setProps({ href: '/some-url' } as Partial<object>)
|
||||
})
|
||||
|
||||
it('has tonal variant', () => {
|
||||
expect(wrapper.classes()).toContain('v-btn--variant-tonal')
|
||||
})
|
||||
})
|
||||
|
||||
describe('click on button', () => {
|
||||
it('calls navigate method with given href', async () => {
|
||||
await wrapper.find('.v-btn').trigger('click')
|
||||
expect(navigate).toHaveBeenCalledWith('/some-path')
|
||||
})
|
||||
})
|
||||
})
|
||||
26
frontend/src/components/VikeBtn.vue
Normal file
26
frontend/src/components/VikeBtn.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<v-btn
|
||||
:variant="isRouteSelected($attrs.href as string) ? 'tonal' : 'flat'"
|
||||
@click.prevent="onClick($attrs.href as string)"
|
||||
>
|
||||
<slot />
|
||||
</v-btn>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { navigate } from 'vike/client/router'
|
||||
|
||||
import { usePageContext } from '#context/usePageContext'
|
||||
|
||||
const pageContext = usePageContext()
|
||||
|
||||
function onClick(href: string) {
|
||||
return navigate(href)
|
||||
}
|
||||
|
||||
const isRouteSelected = (href: string) => {
|
||||
if (href === '/app') {
|
||||
return pageContext.urlPathname.indexOf(href) === 0
|
||||
}
|
||||
return pageContext.urlPathname === href
|
||||
}
|
||||
</script>
|
||||
275
frontend/src/components/__snapshots__/PageShell.test.ts.snap
Normal file
275
frontend/src/components/__snapshots__/PageShell.test.ts.snap
Normal file
@ -0,0 +1,275 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`PageShell > renders 1`] = `
|
||||
<div
|
||||
class="v-application v-theme--light v-layout v-layout--full-height v-locale--is-ltr"
|
||||
>
|
||||
<div
|
||||
class="v-application__wrap"
|
||||
>
|
||||
|
||||
<header
|
||||
class="v-toolbar v-toolbar--flat v-toolbar--density-default v-theme--light v-locale--is-ltr v-app-bar"
|
||||
style="top: 0px; z-index: 1004; transform: translateY(0%); position: fixed; left: 0px; width: calc(100% - 0px - 0px);"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="v-toolbar__content"
|
||||
style="height: 64px;"
|
||||
>
|
||||
<!---->
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="v-row"
|
||||
>
|
||||
<div
|
||||
class="v-col"
|
||||
>
|
||||
<div
|
||||
class="v-avatar v-theme--light v-avatar--density-default v-avatar--variant-flat ma-2 pa-1"
|
||||
style="background-color: #333; color: #fff; caret-color: #fff; width: 48px; height: 48px;"
|
||||
>
|
||||
<div
|
||||
aria-label=""
|
||||
class="v-responsive v-img v-img--booting"
|
||||
>
|
||||
<div
|
||||
class="v-responsive__sizer"
|
||||
/>
|
||||
|
||||
|
||||
<transition-stub
|
||||
appear="true"
|
||||
css="true"
|
||||
name="fade-transition"
|
||||
persisted="false"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="v-img__img v-img__img--cover"
|
||||
src="/src/assets/it4c-logo2-clean-bg_alpha-128x128.png"
|
||||
style="display: none;"
|
||||
/>
|
||||
</transition-stub>
|
||||
<transition-stub
|
||||
appear="false"
|
||||
css="true"
|
||||
name="fade-transition"
|
||||
persisted="false"
|
||||
>
|
||||
<!---->
|
||||
</transition-stub>
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
<span
|
||||
class="v-avatar__underlay"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="v-col d-flex align-center justify-center grow"
|
||||
>
|
||||
<a
|
||||
class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-flat"
|
||||
href="/"
|
||||
>
|
||||
|
||||
<span
|
||||
class="v-btn__overlay"
|
||||
/>
|
||||
<span
|
||||
class="v-btn__underlay"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<span
|
||||
class="v-btn__content"
|
||||
data-no-activator=""
|
||||
>
|
||||
|
||||
|
||||
|
||||
$t('menu.home')
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<!---->
|
||||
<!---->
|
||||
</a>
|
||||
<a
|
||||
class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-flat"
|
||||
href="/app"
|
||||
>
|
||||
|
||||
<span
|
||||
class="v-btn__overlay"
|
||||
/>
|
||||
<span
|
||||
class="v-btn__underlay"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<span
|
||||
class="v-btn__content"
|
||||
data-no-activator=""
|
||||
>
|
||||
|
||||
|
||||
|
||||
$t('menu.app')
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<!---->
|
||||
<!---->
|
||||
</a>
|
||||
<a
|
||||
class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-flat"
|
||||
href="/about"
|
||||
>
|
||||
|
||||
<span
|
||||
class="v-btn__overlay"
|
||||
/>
|
||||
<span
|
||||
class="v-btn__underlay"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<span
|
||||
class="v-btn__content"
|
||||
data-no-activator=""
|
||||
>
|
||||
|
||||
|
||||
|
||||
$t('menu.about')
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<!---->
|
||||
<!---->
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="v-col"
|
||||
>
|
||||
<div
|
||||
class="v-input v-input--horizontal v-input--center-affix v-input--density-default v-locale--is-ltr v-input--dirty v-switch d-flex justify-end mr-5"
|
||||
>
|
||||
<!---->
|
||||
<div
|
||||
class="v-input__control"
|
||||
>
|
||||
|
||||
<div
|
||||
class="v-selection-control v-selection-control--dirty v-selection-control--density-default"
|
||||
>
|
||||
<div
|
||||
class="v-selection-control__wrapper text-success"
|
||||
>
|
||||
|
||||
<div
|
||||
class="v-switch__track bg-success"
|
||||
>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="v-selection-control__input"
|
||||
>
|
||||
|
||||
|
||||
<input
|
||||
aria-describedby="switch-4-messages"
|
||||
aria-disabled="false"
|
||||
id="switch-4"
|
||||
type="checkbox"
|
||||
value="true"
|
||||
/>
|
||||
<div
|
||||
class="v-switch__thumb bg-success"
|
||||
>
|
||||
<transition-stub
|
||||
appear="false"
|
||||
css="true"
|
||||
name="scale-transition"
|
||||
persisted="false"
|
||||
>
|
||||
<!---->
|
||||
</transition-stub>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<label
|
||||
class="v-label v-label--clickable"
|
||||
for="switch-4"
|
||||
>
|
||||
<!---->
|
||||
|
||||
$t('language.german')
|
||||
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!---->
|
||||
<div
|
||||
class="v-input__details"
|
||||
>
|
||||
<transition-group-stub
|
||||
appear="false"
|
||||
aria-live="polite"
|
||||
class="v-messages"
|
||||
css="true"
|
||||
id="switch-4-messages"
|
||||
name="slide-y-transition"
|
||||
persisted="false"
|
||||
role="alert"
|
||||
tag="div"
|
||||
>
|
||||
<!---->
|
||||
</transition-group-stub>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
|
||||
<transition-stub
|
||||
appear="false"
|
||||
css="true"
|
||||
name="expand-transition"
|
||||
persisted="false"
|
||||
>
|
||||
<!---->
|
||||
</transition-stub>
|
||||
|
||||
</header>
|
||||
|
||||
Page Content
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
31
frontend/src/components/__snapshots__/VikeBtn.test.ts.snap
Normal file
31
frontend/src/components/__snapshots__/VikeBtn.test.ts.snap
Normal file
@ -0,0 +1,31 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`VikeBtn > renders 1`] = `
|
||||
<a
|
||||
class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-flat"
|
||||
href="/some-path"
|
||||
>
|
||||
|
||||
<span
|
||||
class="v-btn__overlay"
|
||||
/>
|
||||
<span
|
||||
class="v-btn__underlay"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<span
|
||||
class="v-btn__content"
|
||||
data-no-activator=""
|
||||
>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<!---->
|
||||
<!---->
|
||||
</a>
|
||||
`;
|
||||
20
frontend/src/components/menu/LogoAvatar.stories.ts
Normal file
20
frontend/src/components/menu/LogoAvatar.stories.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { SBComp } from '#types/SBComp'
|
||||
|
||||
import LogoAvatar from './LogoAvatar.vue'
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
||||
|
||||
const meta = {
|
||||
title: 'Menu/LogoAvatar',
|
||||
component: LogoAvatar as SBComp,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {},
|
||||
} satisfies Meta<typeof LogoAvatar>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Example: Story = {
|
||||
args: {},
|
||||
}
|
||||
19
frontend/src/components/menu/LogoAvatar.test.ts
Normal file
19
frontend/src/components/menu/LogoAvatar.test.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { beforeEach, expect, describe, it } from 'vitest'
|
||||
|
||||
import LogoAvatar from './LogoAvatar.vue'
|
||||
|
||||
describe('LogoAvatar', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(LogoAvatar)
|
||||
}
|
||||
let wrapper: ReturnType<typeof Wrapper>
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.element).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
7
frontend/src/components/menu/LogoAvatar.vue
Normal file
7
frontend/src/components/menu/LogoAvatar.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<v-avatar color="#333" class="ma-2 pa-1" :image="Logo" size="48" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Logo from '#assets/it4c-logo2-clean-bg_alpha-128x128.png'
|
||||
</script>
|
||||
20
frontend/src/components/menu/TopMenu.stories.ts
Normal file
20
frontend/src/components/menu/TopMenu.stories.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { SBComp } from '#types/SBComp'
|
||||
|
||||
import TopMenu from './TopMenu.vue'
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
||||
|
||||
const meta = {
|
||||
title: 'Menu/TopMenu',
|
||||
component: TopMenu as SBComp,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {},
|
||||
} satisfies Meta<typeof TopMenu>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Example: Story = {
|
||||
args: {},
|
||||
}
|
||||
18
frontend/src/components/menu/TopMenu.test.ts
Normal file
18
frontend/src/components/menu/TopMenu.test.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { h } from 'vue'
|
||||
import { VApp } from 'vuetify/components'
|
||||
|
||||
import TopMenu from './TopMenu.vue'
|
||||
|
||||
describe('TopMenu', () => {
|
||||
const wrapper = mount(VApp, {
|
||||
slots: {
|
||||
default: h(TopMenu),
|
||||
},
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.element).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
38
frontend/src/components/menu/TopMenu.vue
Normal file
38
frontend/src/components/menu/TopMenu.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<v-app-bar flat>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<LogoAvatar />
|
||||
</v-col>
|
||||
<v-col class="d-flex align-center justify-center grow">
|
||||
<VikeBtn href="/">{{ $t('menu.home') }}</VikeBtn>
|
||||
<VikeBtn href="/app">{{ $t('menu.app') }}</VikeBtn>
|
||||
<VikeBtn href="/about">{{ $t('menu.about') }}</VikeBtn>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-switch
|
||||
v-model="isEnabled"
|
||||
class="d-flex justify-end mr-5"
|
||||
:label="$t('language.german')"
|
||||
color="success"
|
||||
@update:model-value="onChange"
|
||||
></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { useLocale } from 'vuetify'
|
||||
|
||||
import VikeBtn from '#components/VikeBtn.vue'
|
||||
|
||||
import LogoAvatar from './LogoAvatar.vue'
|
||||
|
||||
const { current: locale } = useLocale()
|
||||
const isEnabled = ref(locale.value === 'de')
|
||||
const onChange = () => {
|
||||
locale.value = isEnabled.value ? 'de' : 'en'
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,52 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`LogoAvatar > renders 1`] = `
|
||||
<div
|
||||
class="v-avatar v-theme--light v-avatar--density-default v-avatar--variant-flat ma-2 pa-1"
|
||||
style="background-color: #333; color: #fff; caret-color: #fff; width: 48px; height: 48px;"
|
||||
>
|
||||
<div
|
||||
aria-label=""
|
||||
class="v-responsive v-img v-img--booting"
|
||||
>
|
||||
<div
|
||||
class="v-responsive__sizer"
|
||||
/>
|
||||
|
||||
|
||||
<transition-stub
|
||||
appear="true"
|
||||
css="true"
|
||||
name="fade-transition"
|
||||
persisted="false"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="v-img__img v-img__img--cover"
|
||||
src="/src/assets/it4c-logo2-clean-bg_alpha-128x128.png"
|
||||
style="display: none;"
|
||||
/>
|
||||
</transition-stub>
|
||||
<transition-stub
|
||||
appear="false"
|
||||
css="true"
|
||||
name="fade-transition"
|
||||
persisted="false"
|
||||
>
|
||||
<!---->
|
||||
</transition-stub>
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
<span
|
||||
class="v-avatar__underlay"
|
||||
/>
|
||||
|
||||
</div>
|
||||
`;
|
||||
272
frontend/src/components/menu/__snapshots__/TopMenu.test.ts.snap
Normal file
272
frontend/src/components/menu/__snapshots__/TopMenu.test.ts.snap
Normal file
@ -0,0 +1,272 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`TopMenu > renders 1`] = `
|
||||
<div
|
||||
class="v-application v-theme--light v-layout v-layout--full-height v-locale--is-ltr"
|
||||
>
|
||||
<div
|
||||
class="v-application__wrap"
|
||||
>
|
||||
|
||||
<header
|
||||
class="v-toolbar v-toolbar--flat v-toolbar--density-default v-theme--light v-locale--is-ltr v-app-bar"
|
||||
style="top: 0px; z-index: 1004; transform: translateY(0%); position: fixed; left: 0px; width: calc(100% - 0px - 0px);"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="v-toolbar__content"
|
||||
style="height: 64px;"
|
||||
>
|
||||
<!---->
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="v-row"
|
||||
>
|
||||
<div
|
||||
class="v-col"
|
||||
>
|
||||
<div
|
||||
class="v-avatar v-theme--light v-avatar--density-default v-avatar--variant-flat ma-2 pa-1"
|
||||
style="background-color: #333; color: #fff; caret-color: #fff; width: 48px; height: 48px;"
|
||||
>
|
||||
<div
|
||||
aria-label=""
|
||||
class="v-responsive v-img v-img--booting"
|
||||
>
|
||||
<div
|
||||
class="v-responsive__sizer"
|
||||
/>
|
||||
|
||||
|
||||
<transition-stub
|
||||
appear="true"
|
||||
css="true"
|
||||
name="fade-transition"
|
||||
persisted="false"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="v-img__img v-img__img--cover"
|
||||
src="/src/assets/it4c-logo2-clean-bg_alpha-128x128.png"
|
||||
style="display: none;"
|
||||
/>
|
||||
</transition-stub>
|
||||
<transition-stub
|
||||
appear="false"
|
||||
css="true"
|
||||
name="fade-transition"
|
||||
persisted="false"
|
||||
>
|
||||
<!---->
|
||||
</transition-stub>
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
<span
|
||||
class="v-avatar__underlay"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="v-col d-flex align-center justify-center grow"
|
||||
>
|
||||
<a
|
||||
class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-flat"
|
||||
href="/"
|
||||
>
|
||||
|
||||
<span
|
||||
class="v-btn__overlay"
|
||||
/>
|
||||
<span
|
||||
class="v-btn__underlay"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<span
|
||||
class="v-btn__content"
|
||||
data-no-activator=""
|
||||
>
|
||||
|
||||
|
||||
|
||||
$t('menu.home')
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<!---->
|
||||
<!---->
|
||||
</a>
|
||||
<a
|
||||
class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-flat"
|
||||
href="/app"
|
||||
>
|
||||
|
||||
<span
|
||||
class="v-btn__overlay"
|
||||
/>
|
||||
<span
|
||||
class="v-btn__underlay"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<span
|
||||
class="v-btn__content"
|
||||
data-no-activator=""
|
||||
>
|
||||
|
||||
|
||||
|
||||
$t('menu.app')
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<!---->
|
||||
<!---->
|
||||
</a>
|
||||
<a
|
||||
class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-flat"
|
||||
href="/about"
|
||||
>
|
||||
|
||||
<span
|
||||
class="v-btn__overlay"
|
||||
/>
|
||||
<span
|
||||
class="v-btn__underlay"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
<span
|
||||
class="v-btn__content"
|
||||
data-no-activator=""
|
||||
>
|
||||
|
||||
|
||||
|
||||
$t('menu.about')
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<!---->
|
||||
<!---->
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="v-col"
|
||||
>
|
||||
<div
|
||||
class="v-input v-input--horizontal v-input--center-affix v-input--density-default v-locale--is-ltr v-input--dirty v-switch d-flex justify-end mr-5"
|
||||
>
|
||||
<!---->
|
||||
<div
|
||||
class="v-input__control"
|
||||
>
|
||||
|
||||
<div
|
||||
class="v-selection-control v-selection-control--dirty v-selection-control--density-default"
|
||||
>
|
||||
<div
|
||||
class="v-selection-control__wrapper text-success"
|
||||
>
|
||||
|
||||
<div
|
||||
class="v-switch__track bg-success"
|
||||
>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="v-selection-control__input"
|
||||
>
|
||||
|
||||
|
||||
<input
|
||||
aria-describedby="switch-4-messages"
|
||||
aria-disabled="false"
|
||||
id="switch-4"
|
||||
type="checkbox"
|
||||
value="true"
|
||||
/>
|
||||
<div
|
||||
class="v-switch__thumb bg-success"
|
||||
>
|
||||
<transition-stub
|
||||
appear="false"
|
||||
css="true"
|
||||
name="scale-transition"
|
||||
persisted="false"
|
||||
>
|
||||
<!---->
|
||||
</transition-stub>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<label
|
||||
class="v-label v-label--clickable"
|
||||
for="switch-4"
|
||||
>
|
||||
<!---->
|
||||
|
||||
$t('language.german')
|
||||
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!---->
|
||||
<div
|
||||
class="v-input__details"
|
||||
>
|
||||
<transition-group-stub
|
||||
appear="false"
|
||||
aria-live="polite"
|
||||
class="v-messages"
|
||||
css="true"
|
||||
id="switch-4-messages"
|
||||
name="slide-y-transition"
|
||||
persisted="false"
|
||||
role="alert"
|
||||
tag="div"
|
||||
>
|
||||
<!---->
|
||||
</transition-group-stub>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
|
||||
<transition-stub
|
||||
appear="false"
|
||||
css="true"
|
||||
name="expand-transition"
|
||||
persisted="false"
|
||||
>
|
||||
<!---->
|
||||
</transition-stub>
|
||||
|
||||
</header>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
14
frontend/src/env.test.ts
Normal file
14
frontend/src/env.test.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
import { META } from './env'
|
||||
|
||||
describe('env', () => {
|
||||
it('has correct default values', () => {
|
||||
expect(META).toEqual({
|
||||
BASE_URL: 'http://localhost:3000',
|
||||
DEFAULT_AUTHOR: 'IT Team 4 Change',
|
||||
DEFAULT_DESCRIPTION: 'IT4C Frontend Boilerplate',
|
||||
DEFAULT_TITLE: 'IT4C',
|
||||
})
|
||||
})
|
||||
})
|
||||
10
frontend/src/env.ts
Normal file
10
frontend/src/env.ts
Normal file
@ -0,0 +1,10 @@
|
||||
const META = {
|
||||
BASE_URL: (import.meta.env.PUBLIC_ENV__META__BASE_URL ?? 'http://localhost:3000') as string,
|
||||
DEFAULT_AUTHOR: (import.meta.env.PUBLIC_ENV__META__DEFAULT_AUTHOR ??
|
||||
'IT Team 4 Change') as string,
|
||||
DEFAULT_DESCRIPTION: (import.meta.env.PUBLIC_ENV__META__DEFAULT_DESCRIPTION ??
|
||||
'IT4C Frontend Boilerplate') as string,
|
||||
DEFAULT_TITLE: (import.meta.env.PUBLIC_ENV__META__DEFAULT_TITLE ?? 'IT4C') as string,
|
||||
}
|
||||
|
||||
export { META }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user