create-nuxt-base 0.1.19 → 0.1.21

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +1 -1
  3. package/index.js +22 -29
  4. package/nuxt-base-template/formkit-theme.js +137 -0
  5. package/nuxt-base-template/formkit.config.js +35 -0
  6. package/nuxt-base-template/nuxt.config.ts +34 -3
  7. package/nuxt-base-template/package-lock.json +21304 -0
  8. package/nuxt-base-template/package.json +37 -38
  9. package/nuxt-base-template/src/app.vue +8 -0
  10. package/nuxt-base-template/src/assets/css/tailwind.css +37 -2
  11. package/nuxt-base-template/src/components/ModalShare.vue +67 -0
  12. package/nuxt-base-template/src/components/SocialMediaBubble.vue +16 -0
  13. package/nuxt-base-template/src/components/base/BaseAccordion.vue +52 -0
  14. package/nuxt-base-template/src/components/base/BaseButton.vue +106 -0
  15. package/nuxt-base-template/src/components/base/BaseContainer.vue +5 -0
  16. package/nuxt-base-template/src/components/base/BaseInfinityList.vue +34 -0
  17. package/nuxt-base-template/src/components/base/BaseModalContainer.vue +7 -0
  18. package/nuxt-base-template/src/components/base/BaseNotification.vue +81 -0
  19. package/nuxt-base-template/src/components/base/BaseNotificationContainer.vue +34 -0
  20. package/nuxt-base-template/src/components/base/BaseProgressbar.vue +66 -0
  21. package/nuxt-base-template/src/components/base/BaseToggle.vue +20 -0
  22. package/nuxt-base-template/src/components/transition/TransitionFade.vue +24 -0
  23. package/nuxt-base-template/src/components/transition/TransitionFadeScale.vue +24 -0
  24. package/nuxt-base-template/src/components/transition/TransitionSlide.vue +14 -0
  25. package/nuxt-base-template/src/components/transition/TransitionSlideBottom.vue +14 -0
  26. package/nuxt-base-template/src/components/transition/TransitionSlideRevert.vue +14 -0
  27. package/nuxt-base-template/src/composables/use-auth-fetch.ts +19 -0
  28. package/nuxt-base-template/src/composables/use-file.ts +21 -0
  29. package/nuxt-base-template/src/composables/use-form-helper.ts +100 -0
  30. package/nuxt-base-template/src/composables/use-helper.ts +52 -0
  31. package/nuxt-base-template/src/composables/use-modal.ts +84 -0
  32. package/nuxt-base-template/src/composables/use-notification.ts +29 -0
  33. package/nuxt-base-template/src/composables/use-share.ts +22 -0
  34. package/nuxt-base-template/src/error.vue +53 -0
  35. package/nuxt-base-template/src/forms/inputs/InputCheckbox.vue +29 -0
  36. package/nuxt-base-template/src/forms/inputs/InputFreeTags.vue +98 -0
  37. package/nuxt-base-template/src/forms/inputs/InputImage.vue +65 -0
  38. package/nuxt-base-template/src/forms/inputs/InputTags.vue +112 -0
  39. package/nuxt-base-template/src/forms/inputs/InputToggle.vue +18 -0
  40. package/nuxt-base-template/src/forms/plugins/asterisk-plugin.ts +29 -0
  41. package/nuxt-base-template/src/forms/plugins/scroll-error-plugin.ts +36 -0
  42. package/nuxt-base-template/src/forms/plugins/value-changes-plugin.ts +13 -0
  43. package/nuxt-base-template/src/middleware/admin.global.ts +9 -0
  44. package/nuxt-base-template/src/middleware/auth.global.ts +5 -7
  45. package/nuxt-base-template/src/plugins/4.auth.server.ts +70 -0
  46. package/nuxt-base-template/src/tests/init.test.ts +12 -0
  47. package/nuxt-base-template/tailwind.config.js +42 -3
  48. package/nuxt-base-template/tsconfig.json +4 -1
  49. package/package.json +1 -1
  50. package/nuxt-base-template/cypress.config.ts +0 -54
  51. package/nuxt-base-template/plugins/index.js +0 -5
  52. package/nuxt-base-template/src/components/.gitkeep +0 -0
  53. package/nuxt-base-template/src/components/base/.gitkeep +0 -0
  54. package/nuxt-base-template/src/composables/.gitkeep +0 -0
  55. package/nuxt-base-template/src/forms/.gitkeep +0 -0
  56. package/nuxt-base-template/src/pages/.gitkeep +0 -0
  57. package/nuxt-base-template/src/tests/.gitkeep +0 -0
  58. package/nuxt-base-template/src/tests/hello-world.spec.ts +0 -11
@@ -0,0 +1,112 @@
1
+ <script setup lang="ts">
2
+ interface TagOption {
3
+ label: string;
4
+ value: string;
5
+ }
6
+
7
+ const props = defineProps({
8
+ context: Object,
9
+ });
10
+
11
+ const inputRef = ref();
12
+ const inputValue = ref<string>();
13
+ const inputFocus = ref();
14
+ const name = props.context?.name;
15
+ const placeholder = props.context?.attrs?.placeholder;
16
+ const tags = ref<TagOption[]>([]);
17
+ const defaultOptions: TagOption[] = [...props?.context?.attrs?.options] || [];
18
+ const selectOptions = ref<TagOption[]>([...props?.context?.attrs?.options]);
19
+ patch();
20
+
21
+ function patch() {
22
+ const values = props.context?._value;
23
+ if (!values) {
24
+ return;
25
+ }
26
+
27
+ if (Array.isArray(values)) {
28
+ for (let item of values) {
29
+ handleSelect(item);
30
+ }
31
+ } else {
32
+ handleSelect(values);
33
+ }
34
+ }
35
+
36
+ function handleSelect(value: string) {
37
+ const tag = selectOptions.value.find((e) => e.value === value);
38
+ const included = tags.value?.find((e) => e.value === tag?.value) ?? false;
39
+
40
+ if (!tag) {
41
+ return;
42
+ }
43
+
44
+ if (!included) {
45
+ tags.value.push(tag);
46
+ selectOptions.value = defaultOptions.filter((e) => e.value !== tag.value);
47
+ }
48
+
49
+ props.context?.node.input(tags.value.map((e) => e.value));
50
+ inputValue.value = '';
51
+ }
52
+
53
+ function handleInput(event: any) {
54
+ const value = event.target.value.toLowerCase();
55
+
56
+ if (!value) {
57
+ selectOptions.value = defaultOptions.filter((e: TagOption) => !props.context?._value?.includes(e.value));
58
+ } else {
59
+ selectOptions.value = selectOptions.value.filter((e: TagOption) => e.label.toLowerCase().includes(value));
60
+ }
61
+ }
62
+
63
+ function removeTag(tag: TagOption) {
64
+ tags.value.splice(tags.value.findIndex((e) => e.value === tag.value), 1);
65
+ props.context?.node.input(tags.value.map((e) => e.value));
66
+ selectOptions.value = defaultOptions.filter((e: TagOption) => !props.context?._value?.includes(e.value));
67
+ }
68
+
69
+ function setFocus() {
70
+ inputFocus.value = true;
71
+ inputValue.value = '';
72
+ selectOptions.value = defaultOptions.filter((e: TagOption) => !props.context?._value?.includes(e.value));
73
+ }
74
+ </script>
75
+
76
+ <template>
77
+ <div class="flex flex-col">
78
+ <div class="flex flex-row flex-wrap gap-2">
79
+ <span v-for="tag of tags" :key="tag.value" :class="context?.classes.tag">{{ tag.label }} <span class="i-bi-x" :class="context?.classes.tagIcon" @click="removeTag(tag)"></span> </span>
80
+ </div>
81
+ <div v-if="selectOptions.length" class="relative" :class="context?.classes.inputWrapper">
82
+ <div class="relative w-full flex items-center">
83
+ <input
84
+ :id="props.context?.id"
85
+ ref="inputRef"
86
+ v-model="inputValue"
87
+ autocomplete="off"
88
+ :name="name" type="text" :placeholder="placeholder" :class="context?.classes.input" @focus="setFocus"
89
+ @blur="inputFocus = false" @input="handleInput"
90
+ />
91
+ <span :class="context?.classes.selectIcon" class="formkit-select-icon flex p-[3px] shrink-0 w-5 mr-2 -ml-[1.5em] h-full pointer-events-none formkit-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 7"><path d="M8,6.5c-.13,0-.26-.05-.35-.15L3.15,1.85c-.2-.2-.2-.51,0-.71,.2-.2,.51-.2,.71,0l4.15,4.15L12.15,1.15c.2-.2,.51-.2,.71,0,.2,.2,.2,.51,0,.71l-4.5,4.5c-.1,.1-.23,.15-.35,.15Z" fill="currentColor" /></svg></span>
92
+ </div>
93
+ <TransitionFade :start-duration="200" :leave-duration="200">
94
+ <div
95
+ v-show="inputFocus && defaultOptions.length"
96
+ class="absolute right-0 left-0 z-50 h-fit max-h-12 flex flex-col justify-start items-start"
97
+ :style="`top: ${inputRef?.clientHeight + 1}px`"
98
+ :class="context?.classes.dropdown"
99
+ >
100
+ <div v-if="selectOptions.length" class="w-full">
101
+ <button v-for="option of selectOptions" :key="option.value" :class="context?.classes.dropdownItem" @click="handleSelect(option.value)">
102
+ {{ option.label }}
103
+ </button>
104
+ </div>
105
+ <div v-else :class="context?.classes.noItemsFound">
106
+ Keine Einträge gefunden
107
+ </div>
108
+ </div>
109
+ </TransitionFade>
110
+ </div>
111
+ </div>
112
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps({
3
+ context: Object,
4
+ });
5
+ const inputValue = ref(props?.context?._value ?? false);
6
+
7
+ watch(() => inputValue.value, () => {
8
+ props.context?.node.input(inputValue.value);
9
+ });
10
+ </script>
11
+
12
+ <template>
13
+ <div class="flex justify-between items-center" @click="inputValue = !inputValue">
14
+ <div class="w-8 h-5 flex items-center bg-gray-300 rounded-full p-1 duration-300 ease-in-out" :class="{ 'bg-green-400': inputValue }">
15
+ <div class="bg-white w-4 h-4 rounded-full shadow-md transform duration-300 ease-in-out" :class="{ 'translate-x-2': inputValue }"></div>
16
+ </div>
17
+ </div>
18
+ </template>
@@ -0,0 +1,29 @@
1
+ const isCheckboxAndRadioMultiple = (node: any) => (node.props.type === 'checkbox' || node.props.type === 'radio') && node.props.options;
2
+
3
+ export function addAsteriskPlugin(node: any) {
4
+ node.on('created', () => {
5
+ const isRequired = node.props.parsedRules.some((rule: any) => rule.name === 'required');
6
+ if (!isRequired) {
7
+ return;
8
+ }
9
+
10
+ const isMultiOption = isCheckboxAndRadioMultiple(node);
11
+
12
+ // if we're going to modify the schema then we need
13
+ // to update the schemaMemoKey so we don't get an
14
+ // invalid cached schema.
15
+ node.props.definition.schemaMemoKey = `required_${isMultiOption ? 'multi_' : ''}${node.props.definition.schemaMemoKey}`;
16
+
17
+ const schemaFn = node.props.definition.schema;
18
+ node.props.definition.schema = (sectionsSchema: any = {}) => {
19
+ if (isRequired) {
20
+ if (!isMultiOption) {
21
+ sectionsSchema.label = {
22
+ children: ['$label', ' *'],
23
+ };
24
+ }
25
+ }
26
+ return schemaFn(sectionsSchema);
27
+ };
28
+ });
29
+ }
@@ -0,0 +1,36 @@
1
+ import type { FormKitNode } from '@formkit/core';
2
+
3
+ export function scrollToErrors(node: any) {
4
+ if (node.props.type === 'form') {
5
+ function scrollTo(node: FormKitNode) {
6
+ const el = document.getElementById(node.props.id);
7
+ if (el) {
8
+ el.scrollIntoView({ block: 'center', behavior: 'smooth' });
9
+ }
10
+ }
11
+
12
+ function scrollToErrors() {
13
+ node.walk((child: any) => {
14
+ // Check if this child has errors
15
+ if (child.ledger.value('blocking') || child.ledger.value('errors')) {
16
+ // We found an input with validation errors
17
+ scrollTo(child);
18
+ // Stop searching
19
+ return false;
20
+ }
21
+ }, true);
22
+ }
23
+
24
+ function defaultInvalid(node: FormKitNode) {
25
+ console.error('FormKit::invalidForm', node);
26
+ }
27
+
28
+ const onSubmitInvalid = node.props.onSubmitInvalid ?? defaultInvalid;
29
+ node.props.onSubmitInvalid = () => {
30
+ onSubmitInvalid(node);
31
+ scrollToErrors();
32
+ };
33
+ node.on('unsettled:errors', scrollToErrors);
34
+ }
35
+ return false;
36
+ }
@@ -0,0 +1,13 @@
1
+ import type { FormKitEvent, FormKitNode } from '@formkit/core';
2
+
3
+ export function valueChangesPlugin(node: FormKitNode) {
4
+ const { getDifferences } = useFormHelper();
5
+ if (node.props.type === 'form') {
6
+ setTimeout(() => {
7
+ node.on('commit', (event: FormKitEvent) => {
8
+ const diff = getDifferences(event.origin.props._init, event.payload);
9
+ node.emit('valueChanges', { init: event.origin.props._init, current: event.payload, diff: diff });
10
+ });
11
+ }, 300);
12
+ }
13
+ }
@@ -0,0 +1,9 @@
1
+ export default defineNuxtRouteMiddleware((to, from) => {
2
+ const { currentUserState } = useAuthState();
3
+
4
+ if (to.fullPath.startsWith('/app/admin')) {
5
+ if (!currentUserState?.value?.roles?.includes('admin')) {
6
+ return navigateTo('/app');
7
+ }
8
+ }
9
+ });
@@ -1,11 +1,9 @@
1
1
  export default defineNuxtRouteMiddleware((to, from) => {
2
- const store = useAuthStore();
2
+ const { accessToken } = useAuthState();
3
3
 
4
- if (to.fullPath.startsWith('/auth/')) {
5
- return;
6
- }
7
-
8
- if (!store.token || !store.currentUser) {
9
- return navigateTo('/auth/login');
4
+ if (to.fullPath.startsWith('/app')) {
5
+ if (!accessToken?.value) {
6
+ return navigateTo('/auth/login');
7
+ }
10
8
  }
11
9
  });
@@ -0,0 +1,70 @@
1
+ import { callWithNuxt, defineNuxtPlugin, useNuxtApp, useRuntimeConfig } from 'nuxt/app';
2
+ import { ofetch } from 'ofetch';
3
+
4
+ export default defineNuxtPlugin({
5
+ name: 'auth-server',
6
+ enforce: 'pre',
7
+ async setup() {
8
+ console.debug('4.auth.server.ts::init');
9
+ const _nuxt = useNuxtApp();
10
+ const config = await callWithNuxt(_nuxt, useRuntimeConfig);
11
+ const { refreshToken, accessToken } = await callWithNuxt(_nuxt, useAuthState);
12
+ const { setCurrentUser, getDecodedAccessToken, setTokens } = await callWithNuxt(_nuxt, useAuth);
13
+ const payload = accessToken.value ? getDecodedAccessToken(accessToken.value) : null;
14
+
15
+ if (!refreshToken.value) {
16
+ return;
17
+ }
18
+
19
+ const refreshTokenResult = await ofetch(config.public.host, {
20
+ method: 'POST',
21
+ headers: {
22
+ Authorization: `Bearer ${refreshToken.value}`,
23
+ },
24
+ body: JSON.stringify({
25
+ query: 'mutation refreshToken {refreshToken {token, refreshToken}}',
26
+ variables: {},
27
+ }),
28
+ }).catch((err) => {
29
+ console.error('4.auth.server.ts::refreshToken::catch', err.data);
30
+ });
31
+
32
+ console.debug('4.auth.server.ts::refreshTokenResult', refreshTokenResult);
33
+
34
+ if (refreshTokenResult?.data) {
35
+ const data = refreshTokenResult.data.refreshToken;
36
+ console.debug('4.auth.server.ts::token', data?.token);
37
+ console.debug('4.auth.server.ts::refreshToken', data?.refreshToken);
38
+ setTokens(data.token, data.refreshToken);
39
+
40
+ if (payload?.id) {
41
+ const userResult = await ofetch(config.public.host, {
42
+ method: 'POST',
43
+ headers: {
44
+ Authorization: `Bearer ${refreshTokenResult.data.refreshToken?.token}`,
45
+ },
46
+ body: JSON.stringify({
47
+ query: 'query getUser($id: String!){' +
48
+ 'getUser(id: $id){' +
49
+ 'id ' +
50
+ 'avatar ' +
51
+ 'firstName ' +
52
+ 'lastName ' +
53
+ 'email ' +
54
+ 'roles ' +
55
+ '}}',
56
+ variables: {
57
+ id: payload.id,
58
+ },
59
+ }),
60
+ }).catch((err) => {
61
+ console.error('4.auth.server.ts::getUser::catch', err.data);
62
+ });
63
+ console.debug('4.auth.server.ts::getUser::userResult', userResult);
64
+ if (userResult?.data) {
65
+ setCurrentUser(userResult?.data?.getUser);
66
+ }
67
+ }
68
+ }
69
+ },
70
+ });
@@ -0,0 +1,12 @@
1
+ import { describe, test } from 'vitest';
2
+ import { setup } from '@nuxt/test-utils';
3
+
4
+ describe('Initialization', async () => {
5
+ await setup({
6
+ // test context options
7
+ });
8
+
9
+ test('Init test', () => {
10
+ // ... write tests here
11
+ });
12
+ });
@@ -1,9 +1,48 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
+ const defaultTheme = require('tailwindcss/defaultTheme');
3
+ const { iconsPlugin, getIconCollections } = require('@egoist/tailwindcss-icons');
4
+ const typography = require('@tailwindcss/typography');
5
+ const forms = require('@tailwindcss/forms');
6
+ const formkit = require('@formkit/themes/tailwindcss');
7
+
2
8
  module.exports = {
3
9
  darkMode: "class",
4
- content: [],
10
+ content: ['./formkit-theme.js'],
5
11
  theme: {
6
- extend: {},
12
+ // fontFamily: {
13
+ // montserrat: ['Montserrat'],
14
+ // sans: ['Work Sans', 'Montserrat', ...defaultTheme.fontFamily.sans],
15
+ // serif: ['Work Sans', 'Montserrat', ...defaultTheme.fontFamily.serif],
16
+ // },
17
+ extend: {
18
+ // colors: {
19
+ // primary: {
20
+ // DEFAULT: '#57B39A',
21
+ // 50: '#f3faf7',
22
+ // 100: '#d6f1e7',
23
+ // 200: '#ade2d0',
24
+ // 300: '#7cccb3',
25
+ // 400: '#57b39a',
26
+ // 500: '#37957d',
27
+ // 600: '#2a7765',
28
+ // 700: '#256052',
29
+ // 800: '#224d45',
30
+ // 900: '#20413a',
31
+ // 950: '#0d2621',
32
+ // },
33
+ // }
34
+ screens: {
35
+ '3xl': '2400px'
36
+ }
37
+ }
7
38
  },
8
- plugins: [],
39
+ plugins: [
40
+ typography,
41
+ formkit,
42
+ forms,
43
+ iconsPlugin({
44
+ // Select the icon collections you want to use
45
+ collections: getIconCollections(['bi']),
46
+ }),
47
+ ],
9
48
  };
@@ -1,4 +1,7 @@
1
1
  {
2
2
  // https://nuxt.com/docs/guide/concepts/typescript
3
- "extends": "./.nuxt/tsconfig.json"
3
+ "extends": "./.nuxt/tsconfig.json",
4
+ "compilerOptions": {
5
+ "target": "ES2017"
6
+ }
4
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nuxt-base",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Starter to generate a configured environment with VueJS, Nuxt, Tailwind, Eslint, @lenne.tech/nuxt-base, Unit Tests, Cypress etc.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,54 +0,0 @@
1
- import { addCucumberPreprocessorPlugin } from '@badeball/cypress-cucumber-preprocessor';
2
- import * as webpack from '@cypress/webpack-preprocessor';
3
- import { defineConfig } from 'cypress';
4
-
5
- async function setupNodeEvents(
6
- on: Cypress.PluginEvents,
7
- config: Cypress.PluginConfigOptions,
8
- ): Promise<Cypress.PluginConfigOptions> {
9
- await addCucumberPreprocessorPlugin(on, config);
10
-
11
- on(
12
- 'file:preprocessor',
13
- webpack({
14
- webpackOptions: {
15
- resolve: {
16
- extensions: ['.ts', '.js'],
17
- },
18
- module: {
19
- rules: [
20
- {
21
- test: /\.ts$/,
22
- exclude: [/node_modules/],
23
- use: [
24
- {
25
- loader: 'ts-loader',
26
- },
27
- ],
28
- },
29
- {
30
- test: /\.feature$/,
31
- use: [
32
- {
33
- loader: '@badeball/cypress-cucumber-preprocessor/webpack',
34
- options: config,
35
- },
36
- ],
37
- },
38
- ],
39
- },
40
- },
41
- }),
42
- );
43
-
44
- // Make sure to return the config object as it might have been modified by the plugin.
45
- return config;
46
- }
47
-
48
- export default defineConfig({
49
- e2e: {
50
- setupNodeEvents,
51
- supportFile: false,
52
- specPattern: ['cypress/e2e/**/*.cy.{js,ts}', 'cypress/integrations/**/*.feature'],
53
- },
54
- });
@@ -1,5 +0,0 @@
1
- const cucumber = require('cypress-cucumber-preprocessor').default
2
-
3
- module.exports = (on) => {
4
- on('file:preprocessor', cucumber())
5
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,11 +0,0 @@
1
- import { mount } from '@vue/test-utils';
2
- import { describe, expect, it } from 'vitest';
3
-
4
- import HelloWorld from '../components/hello-world.vue';
5
-
6
- describe('HelloWorld', () => {
7
- it('is a Vue instance', () => {
8
- const wrapper = mount(HelloWorld);
9
- expect(wrapper.vm).toBeTruthy();
10
- });
11
- });