create-nuxt-base 0.3.15 → 0.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/nuxt-base-template/.env.example +2 -1
- package/nuxt-base-template/CLAUDE.md +361 -0
- package/nuxt-base-template/README.md +127 -13
- package/nuxt-base-template/app/app.config.ts +67 -0
- package/nuxt-base-template/app/app.vue +10 -2
- package/nuxt-base-template/app/assets/css/tailwind.css +124 -84
- package/nuxt-base-template/app/components/Modal/ModalBase.vue +65 -0
- package/nuxt-base-template/app/components/Transition/TransitionSlide.vue +0 -2
- package/nuxt-base-template/app/components/Transition/TransitionSlideBottom.vue +0 -2
- package/nuxt-base-template/app/components/Transition/TransitionSlideRevert.vue +0 -2
- package/nuxt-base-template/app/composables/use-file.ts +19 -3
- package/nuxt-base-template/app/composables/use-share.ts +26 -10
- package/nuxt-base-template/app/error.vue +7 -43
- package/nuxt-base-template/app/layouts/default.vue +76 -4
- package/nuxt-base-template/app/layouts/slim.vue +5 -0
- package/nuxt-base-template/app/pages/auth/forgot-password.vue +64 -0
- package/nuxt-base-template/app/pages/auth/login.vue +71 -0
- package/nuxt-base-template/app/pages/auth/reset-password/[token].vue +110 -0
- package/nuxt-base-template/app/pages/index.vue +139 -2
- package/nuxt-base-template/app/public/favicon.ico +0 -0
- package/nuxt-base-template/docs/nuxt.config.ts +4 -0
- package/nuxt-base-template/docs/pages/docs.vue +663 -0
- package/nuxt-base-template/eslint.config.mjs +2 -1
- package/nuxt-base-template/nuxt.config.ts +73 -31
- package/nuxt-base-template/openapi-ts.config.ts +18 -0
- package/nuxt-base-template/package-lock.json +9781 -15157
- package/nuxt-base-template/package.json +30 -35
- package/nuxt-base-template/tsconfig.json +1 -1
- package/package.json +3 -3
- package/nuxt-base-template/app/composables/use-context-menu.ts +0 -19
- package/nuxt-base-template/app/composables/use-form-helper.ts +0 -41
- package/nuxt-base-template/app/composables/use-modal.ts +0 -84
- package/nuxt-base-template/app/composables/use-notification.ts +0 -29
- package/nuxt-base-template/app/middleware/admin.global.ts +0 -9
- package/nuxt-base-template/app/middleware/auth.global.ts +0 -9
- package/nuxt-base-template/app/middleware/logged-in.global.ts +0 -9
- package/nuxt-base-template/app/plugins/auth.server.ts +0 -72
- package/nuxt-base-template/app/plugins/form.plugin.ts +0 -21
- package/nuxt-base-template/app/plugins/pwa.plugin.ts +0 -114
- package/nuxt-base-template/tailwind.config.js +0 -21
|
@@ -9,64 +9,59 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"init": "npm install",
|
|
11
11
|
"reinit": "npx rimraf package-lock.json && npx nuxt cleanup && npx rimraf node_modules && npm cache clean --force && npm i",
|
|
12
|
+
"clean": "npx rimraf .nuxt .output",
|
|
13
|
+
"dev": "nuxt dev",
|
|
12
14
|
"build": "nuxt build",
|
|
13
|
-
"
|
|
14
|
-
"build:
|
|
15
|
-
"build:
|
|
15
|
+
"build:develop": "npx cross-env NODE_ENV=development nuxt build",
|
|
16
|
+
"build:test": "npx cross-env NODE_ENV=test nuxt build",
|
|
17
|
+
"build:prod": "npx cross-env NODE_ENV=production nuxt build",
|
|
16
18
|
"start": "nuxt dev",
|
|
17
19
|
"start:develop": "node .output/server/index.mjs",
|
|
18
20
|
"start:test": "node .output/server/index.mjs",
|
|
19
21
|
"start:prod": "node .output/server/index.mjs",
|
|
20
22
|
"start:tunnel": "nuxt dev --tunnel",
|
|
21
23
|
"start:extern": "npx cross-env HOST=0.0.0.0 nuxt dev",
|
|
22
|
-
"generate-types": "npx rimraf app/base && npx cross-env GENERATE_TYPES=1 nuxt dev",
|
|
23
|
-
"dev": "nuxt dev",
|
|
24
24
|
"generate": "nuxt generate",
|
|
25
|
+
"generate-types": "openapi-ts",
|
|
25
26
|
"preview": "nuxt preview",
|
|
26
27
|
"postinstall": "nuxt prepare",
|
|
27
|
-
"
|
|
28
|
-
"test": "
|
|
28
|
+
"test": "npm run test:e2e",
|
|
29
|
+
"test:e2e": "playwright test",
|
|
29
30
|
"lint": "eslint 'app/**/*.{ts,js,vue}'",
|
|
30
|
-
"lint:fix": "eslint 'app/**/*.{ts,js,vue}' --fix"
|
|
31
|
+
"lint:fix": "eslint 'app/**/*.{ts,js,vue}' --fix",
|
|
32
|
+
"format": "prettier --write .",
|
|
33
|
+
"format:check": "prettier --check .",
|
|
34
|
+
"check": "npm run lint && npm run format:check",
|
|
35
|
+
"fix": "npm run lint:fix && npm run format"
|
|
31
36
|
},
|
|
32
37
|
"dependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@iconify-json/bi": "1.2.6",
|
|
38
|
+
"@hey-api/client-fetch": "0.13.1",
|
|
35
39
|
"@lenne.tech/bug.lt": "latest",
|
|
36
|
-
"@lenne.tech/nuxt-base": "latest",
|
|
37
40
|
"@nuxt/image": "1.11.0",
|
|
38
|
-
"@
|
|
39
|
-
"@
|
|
40
|
-
"@vueuse/integrations": "13.9.0",
|
|
41
|
+
"@nuxt/ui": "4.0.1",
|
|
42
|
+
"@pinia/nuxt": "0.11.2",
|
|
41
43
|
"@vueuse/nuxt": "13.9.0",
|
|
42
|
-
"
|
|
43
|
-
"rimraf": "6.0.1",
|
|
44
|
-
"tailwind-merge": "3.3.1",
|
|
45
|
-
"tailwindcss": "4.1.13",
|
|
46
|
-
"vee-validate": "4.15.1"
|
|
44
|
+
"valibot": "1.1.0"
|
|
47
45
|
},
|
|
48
46
|
"devDependencies": {
|
|
49
|
-
"@
|
|
50
|
-
"@
|
|
47
|
+
"@hey-api/openapi-ts": "0.85.2",
|
|
48
|
+
"@lenne.tech/eslint-config-vue": "2.1.4",
|
|
49
|
+
"@nuxt/devtools": "2.6.5",
|
|
51
50
|
"@nuxt/test-utils": "3.19.2",
|
|
52
51
|
"@nuxtjs/color-mode": "3.5.2",
|
|
53
|
-
"@nuxtjs/google-fonts": "3.2.0",
|
|
54
52
|
"@nuxtjs/plausible": "2.0.1",
|
|
55
|
-
"@nuxtjs/seo": "3.
|
|
56
|
-
"@
|
|
57
|
-
"@
|
|
58
|
-
"@tailwindcss/
|
|
59
|
-
"@
|
|
60
|
-
"@tailwindcss/vite": "4.1.13",
|
|
61
|
-
"@types/node": "24.3.1",
|
|
62
|
-
"@vitejs/plugin-vue": "6.0.1",
|
|
63
|
-
"@vue/test-utils": "2.4.6",
|
|
53
|
+
"@nuxtjs/seo": "3.2.2",
|
|
54
|
+
"@playwright/test": "1.56.1",
|
|
55
|
+
"@tailwindcss/typography": "0.5.19",
|
|
56
|
+
"@tailwindcss/vite": "4.1.14",
|
|
57
|
+
"@types/node": "24.8.1",
|
|
64
58
|
"dayjs-nuxt": "2.1.11",
|
|
65
|
-
"eslint": "9.
|
|
59
|
+
"eslint": "9.37.0",
|
|
66
60
|
"jsdom": "26.1.0",
|
|
67
|
-
"nuxt": "4.1.
|
|
68
|
-
"
|
|
69
|
-
"
|
|
61
|
+
"nuxt": "4.1.3",
|
|
62
|
+
"rimraf": "6.0.1",
|
|
63
|
+
"tailwindcss": "4.1.14",
|
|
64
|
+
"typescript": "5.9.3"
|
|
70
65
|
},
|
|
71
66
|
"exports": {
|
|
72
67
|
".": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-nuxt-base",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Starter to generate a configured environment with VueJS, Nuxt, Tailwind, Eslint,
|
|
3
|
+
"version": "0.3.17",
|
|
4
|
+
"description": "Starter to generate a configured environment with VueJS, Nuxt, Tailwind, Eslint, Unit Tests, Playwright etc.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"cross-spawn": "7.0.6",
|
|
24
|
-
"fs-extra": "11.3.
|
|
24
|
+
"fs-extra": "11.3.2"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"prettier": "3.6.2",
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
const contextMenuState = () => useState<any | null>(() => null);
|
|
2
|
-
|
|
3
|
-
export function useContextMenu() {
|
|
4
|
-
const menu = contextMenuState();
|
|
5
|
-
|
|
6
|
-
const open = (config: { items: any[] }) => {
|
|
7
|
-
menu.value = Object.assign(config, { show: true });
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const close = () => {
|
|
11
|
-
menu.value = null;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
activeMenu: menu,
|
|
16
|
-
close,
|
|
17
|
-
open,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export function useFormHelper() {
|
|
2
|
-
const { close, open } = useModal();
|
|
3
|
-
|
|
4
|
-
function onReload(dirty: boolean, event: BeforeUnloadEvent) {
|
|
5
|
-
if (dirty) {
|
|
6
|
-
event.preventDefault();
|
|
7
|
-
event.returnValue = '';
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async function onNavigate(dirty: boolean) {
|
|
12
|
-
if (dirty) {
|
|
13
|
-
const answer = await openConfirmModal();
|
|
14
|
-
if (!answer) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function openConfirmModal() {
|
|
22
|
-
return new Promise((resolve) => {
|
|
23
|
-
open({
|
|
24
|
-
component: 'ModalConfirm', // TODO: Replace with correct component
|
|
25
|
-
confirm: (confirmed) => {
|
|
26
|
-
resolve(confirmed);
|
|
27
|
-
close();
|
|
28
|
-
},
|
|
29
|
-
data: {
|
|
30
|
-
text: 'Sind Sie sich sicher, dass Sie den Vorgang abbrechen möchten?\n' + 'Es befinden sich ungesicherte Daten auf dieser Seite.',
|
|
31
|
-
title: 'Vorsicht, ungesicherte Daten!',
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
onNavigate,
|
|
39
|
-
onReload,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
export interface ModalContext<T = object> {
|
|
2
|
-
closable?: boolean;
|
|
3
|
-
component: any;
|
|
4
|
-
confirm?: (confirmed: boolean) => void;
|
|
5
|
-
data?: T;
|
|
6
|
-
show: boolean;
|
|
7
|
-
showInner: boolean;
|
|
8
|
-
size?: 'auto' | 'lg' | 'md' | 'sm';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface Modal<T> {
|
|
12
|
-
closable?: boolean;
|
|
13
|
-
component: any;
|
|
14
|
-
confirm?: (confirmed: boolean) => void;
|
|
15
|
-
data?: T;
|
|
16
|
-
size?: 'auto' | 'lg' | 'md' | 'sm';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const modalState = <T>() => useState<ModalContext<T> | null>(() => null);
|
|
20
|
-
|
|
21
|
-
export function useModal<T = object>() {
|
|
22
|
-
const modal = modalState<T>();
|
|
23
|
-
|
|
24
|
-
const open = (modalConfig: Modal<T>) => {
|
|
25
|
-
let delay = 0;
|
|
26
|
-
if (modal.value) {
|
|
27
|
-
delay = 130;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const data = Object.assign(modalConfig, { show: false, showInner: false }) as ModalContext<T>;
|
|
31
|
-
data.size ??= 'md';
|
|
32
|
-
data.closable ??= true;
|
|
33
|
-
|
|
34
|
-
setTimeout(() => {
|
|
35
|
-
modal.value = data;
|
|
36
|
-
}, delay);
|
|
37
|
-
|
|
38
|
-
// Disable scrolling of background
|
|
39
|
-
document.body.style.overflowY = 'hidden';
|
|
40
|
-
|
|
41
|
-
setTimeout(() => {
|
|
42
|
-
if (modal.value) {
|
|
43
|
-
modal.value.show = true;
|
|
44
|
-
}
|
|
45
|
-
}, 30 + delay);
|
|
46
|
-
|
|
47
|
-
setTimeout(() => {
|
|
48
|
-
if (modal.value) {
|
|
49
|
-
modal.value.showInner = true;
|
|
50
|
-
}
|
|
51
|
-
}, 100 + delay);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const close = (duration?: number) => {
|
|
55
|
-
let animationDuration = duration;
|
|
56
|
-
|
|
57
|
-
if (typeof animationDuration !== 'number') {
|
|
58
|
-
animationDuration = 100;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (modal.value) {
|
|
62
|
-
modal.value.showInner = false;
|
|
63
|
-
|
|
64
|
-
setTimeout(() => {
|
|
65
|
-
if (modal.value) {
|
|
66
|
-
modal.value.show = false;
|
|
67
|
-
}
|
|
68
|
-
}, 30 + animationDuration);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Deactivate scrolling of background
|
|
72
|
-
document.body.style.overflowY = '';
|
|
73
|
-
|
|
74
|
-
setTimeout(() => {
|
|
75
|
-
modal.value = null;
|
|
76
|
-
}, 130 + animationDuration);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
activeModal: modal,
|
|
81
|
-
close,
|
|
82
|
-
open,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'uncrypto';
|
|
2
|
-
|
|
3
|
-
interface Notification {
|
|
4
|
-
duration?: number;
|
|
5
|
-
text?: string;
|
|
6
|
-
title: string;
|
|
7
|
-
type: 'error' | 'info' | 'success' | 'warning';
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const notificationState = () => useState<Array<Notification & { uuid: string }>>(() => []);
|
|
11
|
-
|
|
12
|
-
export function useNotification() {
|
|
13
|
-
const notifications = notificationState();
|
|
14
|
-
const notify = (message: Notification) => {
|
|
15
|
-
const data = Object.assign(message, { uuid: randomUUID() });
|
|
16
|
-
data.duration ??= 5000;
|
|
17
|
-
notifications.value.push(data);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const remove = (uuid: string) => {
|
|
21
|
-
notifications.value = notifications.value.filter((n) => n.uuid !== uuid);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
notifications,
|
|
26
|
-
notify,
|
|
27
|
-
remove,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { callWithNuxt, defineNuxtPlugin, useNuxtApp, useRuntimeConfig } from 'nuxt/app';
|
|
2
|
-
import { ofetch } from 'ofetch';
|
|
3
|
-
|
|
4
|
-
export default defineNuxtPlugin({
|
|
5
|
-
dependsOn: ['cookies', 'graphql-meta'], // from nuxt-base
|
|
6
|
-
name: 'auth-server',
|
|
7
|
-
async setup() {
|
|
8
|
-
const _nuxt = useNuxtApp();
|
|
9
|
-
const config = await callWithNuxt(_nuxt, useRuntimeConfig);
|
|
10
|
-
const { accessTokenState, currentUserState, refreshTokenState } = await callWithNuxt(_nuxt, useAuthState);
|
|
11
|
-
const { clearSession, getDecodedAccessToken, isTokenExpired, setCurrentUser, setTokens } = await callWithNuxt(_nuxt, useAuth);
|
|
12
|
-
const payload = accessTokenState.value ? getDecodedAccessToken(accessTokenState.value) : null;
|
|
13
|
-
|
|
14
|
-
if (!accessTokenState.value || !refreshTokenState.value || currentUserState.value) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let token = accessTokenState.value;
|
|
19
|
-
if (isTokenExpired(accessTokenState.value)) {
|
|
20
|
-
const refreshTokenResult = await ofetch(config.public.gqlHost, {
|
|
21
|
-
body: JSON.stringify({
|
|
22
|
-
query: 'mutation refreshToken {refreshToken {token, refreshToken}}',
|
|
23
|
-
variables: {},
|
|
24
|
-
}),
|
|
25
|
-
headers: {
|
|
26
|
-
Authorization: `Bearer ${refreshTokenState.value}`,
|
|
27
|
-
},
|
|
28
|
-
method: 'POST',
|
|
29
|
-
}).catch((err) => {
|
|
30
|
-
console.error('2.auth.server.ts::refreshToken::catch', err.data);
|
|
31
|
-
clearSession();
|
|
32
|
-
navigateTo('/auth');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const data = refreshTokenResult?.data?.refreshToken;
|
|
36
|
-
if (data) {
|
|
37
|
-
setTokens(data.token, data.refreshToken);
|
|
38
|
-
token = data?.token;
|
|
39
|
-
} else {
|
|
40
|
-
clearSession();
|
|
41
|
-
await navigateTo('/auth');
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (token && payload?.id) {
|
|
46
|
-
const userResult = await ofetch(config.public.gqlHost, {
|
|
47
|
-
body: JSON.stringify({
|
|
48
|
-
query: 'query getUser($id: String!){' + 'getUser(id: $id){' + 'id ' + 'firstName ' + 'lastName ' + 'email ' + 'roles ' + '}}',
|
|
49
|
-
variables: {
|
|
50
|
-
id: payload.id,
|
|
51
|
-
},
|
|
52
|
-
}),
|
|
53
|
-
headers: {
|
|
54
|
-
Authorization: `Bearer ${token}`,
|
|
55
|
-
},
|
|
56
|
-
method: 'POST',
|
|
57
|
-
}).catch((err) => {
|
|
58
|
-
console.error('2.auth.server.ts::getUser::catch', err);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (userResult?.errors) {
|
|
62
|
-
clearSession();
|
|
63
|
-
navigateTo('/auth');
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (userResult?.data) {
|
|
68
|
-
setCurrentUser(userResult?.data?.getUser);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { setLocale } from 'yup';
|
|
2
|
-
|
|
3
|
-
export default defineNuxtPlugin(async () => {
|
|
4
|
-
setLocale({
|
|
5
|
-
// use constant translation keys for messages without values
|
|
6
|
-
mixed: {
|
|
7
|
-
default: 'Dieses Feld ist ungültig.',
|
|
8
|
-
notType: 'Dieses Feld ist ungültig.',
|
|
9
|
-
required: 'Dieses Feld ist erforderlich.',
|
|
10
|
-
},
|
|
11
|
-
string: {
|
|
12
|
-
email: 'Bitte geben Sie eine gültige E-Mail-Adresse ein.',
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// Custom validation rules
|
|
17
|
-
// https://github.com/jquense/yup?tab=readme-ov-file#addmethodschematype-schema-name-string-method--schema-void
|
|
18
|
-
// addMethod(string, 'plz', () => {
|
|
19
|
-
// return
|
|
20
|
-
// });
|
|
21
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
export default defineNuxtPlugin(async () => {
|
|
2
|
-
const applicationServerKey = useRuntimeConfig().public.webPushKey as string;
|
|
3
|
-
const permissionState = ref<PermissionState>('prompt');
|
|
4
|
-
const subscription = ref<null | PushSubscription>(null);
|
|
5
|
-
|
|
6
|
-
if (process.client) {
|
|
7
|
-
const iosPWASplash = (await import('ios-pwa-splash')).default;
|
|
8
|
-
iosPWASplash('/notification.png', '#FFFFFF');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async function subscribe() {
|
|
12
|
-
const sw = await navigator.serviceWorker.ready;
|
|
13
|
-
const push = await sw.pushManager.subscribe({
|
|
14
|
-
applicationServerKey,
|
|
15
|
-
userVisibleOnly: true,
|
|
16
|
-
});
|
|
17
|
-
await refreshSubscription();
|
|
18
|
-
await refreshPermissionState();
|
|
19
|
-
const body = {
|
|
20
|
-
payload: push,
|
|
21
|
-
};
|
|
22
|
-
try {
|
|
23
|
-
await useAuthFetch('/web-push/subscribe', {
|
|
24
|
-
baseURL: process.env.API_URL,
|
|
25
|
-
body,
|
|
26
|
-
method: 'POST',
|
|
27
|
-
});
|
|
28
|
-
} catch (e) {
|
|
29
|
-
console.error(e);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function refreshPermissionState() {
|
|
34
|
-
try {
|
|
35
|
-
const sw = await navigator.serviceWorker.ready;
|
|
36
|
-
permissionState.value = await sw.pushManager.permissionState({
|
|
37
|
-
applicationServerKey,
|
|
38
|
-
userVisibleOnly: true,
|
|
39
|
-
});
|
|
40
|
-
} catch (e) {
|
|
41
|
-
console.error(e);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function refreshSubscription() {
|
|
46
|
-
try {
|
|
47
|
-
const sw = await navigator.serviceWorker.ready;
|
|
48
|
-
subscription.value = await sw.pushManager.getSubscription();
|
|
49
|
-
} catch (e) {
|
|
50
|
-
console.error(e);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function unsubscribe() {
|
|
55
|
-
if (subscription.value) {
|
|
56
|
-
await subscription.value.unsubscribe();
|
|
57
|
-
const body = {
|
|
58
|
-
payload: subscription.value,
|
|
59
|
-
};
|
|
60
|
-
await refreshPermissionState();
|
|
61
|
-
try {
|
|
62
|
-
await useAuthFetch('/web-push/', {
|
|
63
|
-
baseURL: process.env.API_URL,
|
|
64
|
-
body,
|
|
65
|
-
method: 'DELETE',
|
|
66
|
-
});
|
|
67
|
-
} catch (e) {
|
|
68
|
-
console.error(e);
|
|
69
|
-
}
|
|
70
|
-
subscription.value = null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const route = useRoute();
|
|
75
|
-
const pwa = ref(false);
|
|
76
|
-
const isPwa = computed(() => route.query.standalone === 'true' || pwa.value);
|
|
77
|
-
|
|
78
|
-
if (process.client) {
|
|
79
|
-
if (window && window.matchMedia && document && window.matchMedia('(display-mode: standalone)').matches) {
|
|
80
|
-
if (navigator && navigator.serviceWorker) {
|
|
81
|
-
await refreshSubscription();
|
|
82
|
-
await refreshPermissionState();
|
|
83
|
-
}
|
|
84
|
-
pwa.value = true;
|
|
85
|
-
document.body.classList.add('select-none', 'overscroll-y-none', 'no-scrollbar', 'bg-rm-gray-1');
|
|
86
|
-
} else {
|
|
87
|
-
pwa.value = false;
|
|
88
|
-
document.body.classList.remove('select-none', 'overscroll-y-none', 'no-scrollbar', 'bg-rm-gray-1');
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
useHead({
|
|
93
|
-
htmlAttrs: { class: isPwa.value ? 'pwa' : '' },
|
|
94
|
-
meta: [
|
|
95
|
-
{
|
|
96
|
-
content: 'width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no',
|
|
97
|
-
hid: 'viewport',
|
|
98
|
-
name: 'viewport',
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
provide: {
|
|
105
|
-
pwa: {
|
|
106
|
-
isPwa: () => isPwa.value,
|
|
107
|
-
permissionState,
|
|
108
|
-
subscribe,
|
|
109
|
-
subscription,
|
|
110
|
-
unsubscribe,
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/** @type {import('tailwindcss').Config} */
|
|
2
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
3
|
-
const { iconsPlugin, getIconCollections } = require('@egoist/tailwindcss-icons');
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
5
|
-
const plugin = require('tailwindcss/plugin');
|
|
6
|
-
|
|
7
|
-
module.exports = {
|
|
8
|
-
plugins: [
|
|
9
|
-
iconsPlugin({
|
|
10
|
-
// Select the icon collections you want to use
|
|
11
|
-
collections: getIconCollections(['bi']),
|
|
12
|
-
}),
|
|
13
|
-
plugin(({ addVariant, e }) => {
|
|
14
|
-
addVariant('pwa', ({ modifySelectors, separator }) => {
|
|
15
|
-
modifySelectors(({ className }) => {
|
|
16
|
-
return `.pwa .${e(`pwa${separator}${className}`)}`;
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
}),
|
|
20
|
-
],
|
|
21
|
-
};
|