@goweekdays/layer-core 1.0.0

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 (60) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.github/workflows/main.yml +19 -0
  4. package/.github/workflows/publish.yml +39 -0
  5. package/CHANGELOG.md +7 -0
  6. package/README.md +75 -0
  7. package/app/app.vue +8 -0
  8. package/app/assets/fonts/ProductSans-Black.ttf +0 -0
  9. package/app/assets/fonts/ProductSans-BlackItalic.ttf +0 -0
  10. package/app/assets/fonts/ProductSans-Bold.ttf +0 -0
  11. package/app/assets/fonts/ProductSans-BoldItalic.ttf +0 -0
  12. package/app/assets/fonts/ProductSans-Italic.ttf +0 -0
  13. package/app/assets/fonts/ProductSans-Light.ttf +0 -0
  14. package/app/assets/fonts/ProductSans-LightItalic.ttf +0 -0
  15. package/app/assets/fonts/ProductSans-Medium.ttf +0 -0
  16. package/app/assets/fonts/ProductSans-MediumItalic.ttf +0 -0
  17. package/app/assets/fonts/ProductSans-Regular.ttf +0 -0
  18. package/app/assets/fonts/ProductSans-Thin.ttf +0 -0
  19. package/app/assets/fonts/ProductSans-ThinItalic.ttf +0 -0
  20. package/app/assets/main.css +10 -0
  21. package/app/assets/settings.scss +89 -0
  22. package/app/components/Input/ListGroupSelection.vue +107 -0
  23. package/app/components/Input/Password.vue +35 -0
  24. package/app/composables/useLocalAuth.ts +131 -0
  25. package/app/composables/useLocalSetup.ts +34 -0
  26. package/app/composables/useMember.ts +100 -0
  27. package/app/composables/useRole.ts +87 -0
  28. package/app/composables/useUtils.ts +308 -0
  29. package/app/layouts/plain.vue +7 -0
  30. package/app/middleware/01.auth.ts +14 -0
  31. package/app/middleware/org.ts +13 -0
  32. package/app/pages/forgot-password.vue +76 -0
  33. package/app/pages/index.vue +3 -0
  34. package/app/pages/login.vue +134 -0
  35. package/app/pages/logout.vue +18 -0
  36. package/app/pages/privacy-policy.vue +23 -0
  37. package/app/pages/refund-policy.vue +23 -0
  38. package/app/pages/sign-up.vue +141 -0
  39. package/app/pages/terms-and-conditions.vue +23 -0
  40. package/app/plugins/member.client.ts +66 -0
  41. package/app/plugins/secure.client.ts +34 -0
  42. package/app/plugins/vuetify.ts +54 -0
  43. package/app/public/background.png +0 -0
  44. package/app/public/favicon.svg +1 -0
  45. package/app/public/logo.png +0 -0
  46. package/app/public/pwa-192x192.png +0 -0
  47. package/app/public/pwa-512x512.png +0 -0
  48. package/app/public/robots.txt +1 -0
  49. package/app/types/local.d.ts +113 -0
  50. package/app/types/member.d.ts +13 -0
  51. package/app/types/role.d.ts +12 -0
  52. package/content/privacy-policy.md +151 -0
  53. package/content/refund-policy.md +65 -0
  54. package/content/terms-and-conditions.md +137 -0
  55. package/content.config.ts +10 -0
  56. package/nuxt.config.ts +45 -0
  57. package/package.json +32 -0
  58. package/public/favicon.ico +0 -0
  59. package/public/robots.txt +2 -0
  60. package/tsconfig.json +18 -0
@@ -0,0 +1,8 @@
1
+ # Changesets
2
+
3
+ Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4
+ with multi-package repos, or single-package repos to help you version and publish your code. You can
5
+ find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6
+
7
+ We have a quick list of common questions to get you started engaging with this project in
8
+ [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json",
3
+ "changelog": "@changesets/cli/changelog",
4
+ "commit": false,
5
+ "fixed": [],
6
+ "linked": [],
7
+ "access": "restricted",
8
+ "baseBranch": "main",
9
+ "updateInternalDependencies": "patch",
10
+ "ignore": []
11
+ }
@@ -0,0 +1,19 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ paths:
7
+ - ".changeset/**"
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-node@v4
15
+ with:
16
+ node-version: 20.x
17
+ cache: "yarn"
18
+ - run: yarn install --frozen-lockfile && yarn postinstall
19
+ - run: yarn generate
@@ -0,0 +1,39 @@
1
+ name: Publish
2
+ on:
3
+ workflow_run:
4
+ workflows: [CI]
5
+ branches: [main]
6
+ types: [completed]
7
+
8
+ concurrency: ${{ github.workflow }}-${{ github.ref }}
9
+
10
+ permissions:
11
+ contents: write
12
+ pull-requests: write
13
+
14
+ jobs:
15
+ publish:
16
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: actions/setup-node@v4
22
+ with:
23
+ node-version: 20.x
24
+ cache: "yarn" # Use yarn for caching
25
+ cache-dependency-path: yarn.lock # Cache Yarn lockfile
26
+
27
+ - run: yarn install --immutable # Install dependencies with immutable lockfile
28
+
29
+ - name: Create .npmrc for publishing
30
+ run: |
31
+ echo '//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}' > ~/.npmrc
32
+
33
+ - name: Create Release Pull Request or Publish
34
+ id: changesets
35
+ uses: changesets/action@v1
36
+ with:
37
+ publish: yarn release # Use yarn for publishing
38
+ env:
39
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @goweekdays/layer-core
2
+
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 7c21048: Initial release
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Nuxt Minimal Starter
2
+
3
+ Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
4
+
5
+ ## Setup
6
+
7
+ Make sure to install dependencies:
8
+
9
+ ```bash
10
+ # npm
11
+ npm install
12
+
13
+ # pnpm
14
+ pnpm install
15
+
16
+ # yarn
17
+ yarn install
18
+
19
+ # bun
20
+ bun install
21
+ ```
22
+
23
+ ## Development Server
24
+
25
+ Start the development server on `http://localhost:3000`:
26
+
27
+ ```bash
28
+ # npm
29
+ npm run dev
30
+
31
+ # pnpm
32
+ pnpm dev
33
+
34
+ # yarn
35
+ yarn dev
36
+
37
+ # bun
38
+ bun run dev
39
+ ```
40
+
41
+ ## Production
42
+
43
+ Build the application for production:
44
+
45
+ ```bash
46
+ # npm
47
+ npm run build
48
+
49
+ # pnpm
50
+ pnpm build
51
+
52
+ # yarn
53
+ yarn build
54
+
55
+ # bun
56
+ bun run build
57
+ ```
58
+
59
+ Locally preview production build:
60
+
61
+ ```bash
62
+ # npm
63
+ npm run preview
64
+
65
+ # pnpm
66
+ pnpm preview
67
+
68
+ # yarn
69
+ yarn preview
70
+
71
+ # bun
72
+ bun run preview
73
+ ```
74
+
75
+ Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
package/app/app.vue ADDED
@@ -0,0 +1,8 @@
1
+ <template>
2
+ <ClientOnly>
3
+ <NuxtLoadingIndicator />
4
+ <NuxtLayout>
5
+ <NuxtPage />
6
+ </NuxtLayout>
7
+ </ClientOnly>
8
+ </template>
@@ -0,0 +1,10 @@
1
+ input[type="number"]::-webkit-inner-spin-button,
2
+ input[type="number"]::-webkit-outer-spin-button {
3
+ -webkit-appearance: none;
4
+ margin: 0;
5
+ }
6
+
7
+ .v-list-item--density-default.v-list-item--three-line .v-list-item__prepend,
8
+ .v-list-item--density-default.v-list-item--three-line .v-list-item__append {
9
+ padding-top: 0 !important;
10
+ }
@@ -0,0 +1,89 @@
1
+ @use 'vuetify/settings' with (
2
+ // variables go here
3
+ $body-font-family: ('ProductSans', sans-serif),
4
+ $heading-font-family: ('ProductSans', sans-serif)
5
+ );
6
+
7
+ @font-face {
8
+ font-family: 'ProductSans';
9
+ src: url('/assets/fonts/ProductSans-Black.ttf') format('truetype');
10
+ font-weight: 900;
11
+ font-style: normal;
12
+ }
13
+
14
+ @font-face {
15
+ font-family: 'ProductSans';
16
+ src: url('/assets/fonts/ProductSans-BlackItalic.ttf') format('truetype');
17
+ font-weight: 900;
18
+ font-style: italic;
19
+ }
20
+
21
+ @font-face {
22
+ font-family: 'ProductSans';
23
+ src: url('/assets/fonts/ProductSans-Bold.ttf') format('truetype');
24
+ font-weight: bold;
25
+ font-style: normal;
26
+ }
27
+
28
+ @font-face {
29
+ font-family: 'ProductSans';
30
+ src: url('/assets/fonts/ProductSans-BoldItalic.ttf') format('truetype');
31
+ font-weight: bold;
32
+ font-style: italic;
33
+ }
34
+
35
+ @font-face {
36
+ font-family: 'ProductSans';
37
+ src: url('/assets/fonts/ProductSans-Italic.ttf') format('truetype');
38
+ font-weight: normal;
39
+ font-style: italic;
40
+ }
41
+
42
+ @font-face {
43
+ font-family: 'ProductSans';
44
+ src: url('/assets/fonts/ProductSans-Light.ttf') format('truetype');
45
+ font-weight: 300;
46
+ font-style: normal;
47
+ }
48
+
49
+ @font-face {
50
+ font-family: 'ProductSans';
51
+ src: url('/assets/fonts/ProductSans-LightItalic.ttf') format('truetype');
52
+ font-weight: 300;
53
+ font-style: italic;
54
+ }
55
+
56
+ @font-face {
57
+ font-family: 'ProductSans';
58
+ src: url('/assets/fonts/ProductSans-Medium.ttf') format('truetype');
59
+ font-weight: 500;
60
+ font-style: normal;
61
+ }
62
+
63
+ @font-face {
64
+ font-family: 'ProductSans';
65
+ src: url('/assets/fonts/ProductSans-MediumItalic.ttf') format('truetype');
66
+ font-weight: 500;
67
+ font-style: italic;
68
+ }
69
+
70
+ @font-face {
71
+ font-family: 'ProductSans';
72
+ src: url('/assets/fonts/ProductSans-Regular.ttf') format('truetype');
73
+ font-weight: normal;
74
+ font-style: normal;
75
+ }
76
+
77
+ @font-face {
78
+ font-family: 'ProductSans';
79
+ src: url('/assets/fonts/ProductSans-Thin.ttf') format('truetype');
80
+ font-weight: 100;
81
+ font-style: normal;
82
+ }
83
+
84
+ @font-face {
85
+ font-family: 'ProductSans';
86
+ src: url('/assets/fonts/ProductSans-ThinItalic.ttf') format('truetype');
87
+ font-weight: 100;
88
+ font-style: italic;
89
+ }
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <v-input v-bind="attrs">
3
+ <v-card width="100%" v-bind="attrs">
4
+ <v-list
5
+ v-model:selected="selected"
6
+ lines="two"
7
+ :select-strategy="attrs.readonly ? 'classic' : 'leaf'"
8
+ class="pa-0"
9
+ density="compact"
10
+ read-only
11
+ open-strategy="single"
12
+ >
13
+ <template name="prepend"></template>
14
+
15
+ <template
16
+ v-for="(permission, permissionKey, permissionIndex) in props.items"
17
+ :key="permissionKey"
18
+ >
19
+ <v-divider v-if="permissionIndex > 0"></v-divider>
20
+ <v-list-group :value="permissionKey" fluid>
21
+ <template v-slot:activator="{ props }">
22
+ <v-list-item v-bind="props" density="compact">
23
+ <span class="text-capitalize">
24
+ {{ String(permissionKey).replace(/-/g, " ") }}
25
+ </span>
26
+
27
+ <template #prepend>
28
+ <v-chip class="mr-2" small>
29
+ {{ selectedActionCount(String(permissionKey)) }}
30
+ </v-chip>
31
+ </template>
32
+ </v-list-item>
33
+ </template>
34
+
35
+ <template v-for="(item, itemKey) in permission" :key="itemKey">
36
+ <v-divider></v-divider>
37
+ <v-list-item v-if="attrs.readonly" density="compact">
38
+ <template #title class="pl-2">
39
+ <span class="text-subtitle-2 text-capitalize">
40
+ {{ String(itemKey).replace(/-/g, " ") }}
41
+ </span>
42
+ </template>
43
+
44
+ <template #subtitle class="pl-2">
45
+ <span class="text-subtitle-2">{{ item.description }}</span>
46
+ </template>
47
+ </v-list-item>
48
+
49
+ <v-list-item
50
+ v-else
51
+ :value="`${permissionKey}:${itemKey}`"
52
+ density="compact"
53
+ >
54
+ <template #title class="pl-2">
55
+ <span class="text-subtitle-2 text-capitalize">
56
+ {{ String(itemKey).replace(/-/g, " ") }}
57
+ </span>
58
+ </template>
59
+
60
+ <template #subtitle class="pl-2">
61
+ <span class="text-subtitle-2 text-capitalize">
62
+ {{ String(item.description).replace(/-/g, " ") }}
63
+ </span>
64
+ </template>
65
+
66
+ <template #prepend="{ isSelected }">
67
+ <v-list-item-action start class="pl-1">
68
+ <v-checkbox-btn :model-value="isSelected"></v-checkbox-btn>
69
+ </v-list-item-action>
70
+ </template>
71
+ </v-list-item>
72
+ </template>
73
+ </v-list-group>
74
+ </template>
75
+
76
+ <slot v-if="noData" name="no-data">
77
+ <v-list-item>
78
+ <v-list-item-title>No data</v-list-item-title>
79
+ </v-list-item>
80
+ </slot>
81
+
82
+ <template name="append"></template>
83
+ </v-list>
84
+ </v-card>
85
+ </v-input>
86
+ </template>
87
+
88
+ <script setup lang="ts">
89
+ const selected = defineModel<Array<string>>({ default: [] });
90
+ const attrs = useAttrs();
91
+ const props = defineProps({
92
+ items: {
93
+ type: Object,
94
+ required: true,
95
+ default: () => ({}),
96
+ },
97
+ });
98
+
99
+ const selectedActionCount = (resource: string) => {
100
+ return selected.value.filter((permission) => permission.startsWith(resource))
101
+ .length;
102
+ };
103
+
104
+ const noData = computed(() => {
105
+ return Object.keys(props.items).length === 0;
106
+ });
107
+ </script>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <v-text-field
3
+ id="_password"
4
+ :model-value="modelValue"
5
+ :type="showPassword ? 'text' : 'password'"
6
+ :append-inner-icon="showPassword ? 'mdi-eye-off' : 'mdi-eye'"
7
+ @click:append-inner="togglePasswordVisibility"
8
+ @update:model-value="updatePassword"
9
+ v-bind="$attrs"
10
+ autocomplete="off"
11
+ />
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ // Define the props for the v-model
16
+ defineProps({
17
+ modelValue: {
18
+ type: String,
19
+ },
20
+ });
21
+
22
+ // Define emit for updating v-model
23
+ const emit = defineEmits(["update:modelValue"]);
24
+
25
+ // Control password visibility
26
+ const showPassword = ref(false);
27
+ const togglePasswordVisibility = () => {
28
+ showPassword.value = !showPassword.value;
29
+ };
30
+
31
+ // Handle input update and emit to parent
32
+ const updatePassword = (value: string) => {
33
+ emit("update:modelValue", value);
34
+ };
35
+ </script>
@@ -0,0 +1,131 @@
1
+ export default function useLocalAuth() {
2
+ const { cookieConfig } = useLocalSetup();
3
+
4
+ const currentUser = useState<TUser | null>("currentUser", () => null);
5
+ const membership = useState<TMember | null>("membership", () => null);
6
+ const permissions = useState<string[]>("permissions", () => []);
7
+
8
+ const hasPermission = (permissionKey: string): ComputedRef<boolean> => {
9
+ return computed(() => {
10
+ if (!permissions.value.length) return false;
11
+ return permissions.value.some((perm) => perm === permissionKey);
12
+ });
13
+ };
14
+
15
+ function authenticate() {
16
+ if (currentUser.value) {
17
+ return;
18
+ }
19
+
20
+ const user = useCookie("user", cookieConfig).value;
21
+
22
+ const { data: getCurrentUserReq, error: getCurrentUserErr } =
23
+ useLazyAsyncData("get-current-user", () =>
24
+ $fetch<TUser>(`/api/users/id/${user}`)
25
+ );
26
+
27
+ watchEffect(() => {
28
+ if (getCurrentUserReq.value) {
29
+ currentUser.value = getCurrentUserReq.value;
30
+ }
31
+ });
32
+
33
+ watchEffect(() => {
34
+ if (getCurrentUserErr.value) {
35
+ // Redirect to login page if user authentication fails
36
+ navigateTo({ name: "index" });
37
+ }
38
+ });
39
+ }
40
+
41
+ async function login({ email = "", password = "", role = "" }) {
42
+ return $fetch<Record<string, any>>("/api/auth/login", {
43
+ method: "POST",
44
+ body: JSON.stringify({ email, password, role }),
45
+ });
46
+ }
47
+
48
+ async function logout() {
49
+ useCookie("sid", cookieConfig).value = null;
50
+ useCookie("user", cookieConfig).value = null;
51
+ useCookie("role", cookieConfig).value = null;
52
+ }
53
+
54
+ async function getCurrentUser() {
55
+ const user = useCookie("user", cookieConfig).value;
56
+ if (!user) return null;
57
+ try {
58
+ const _user = await $fetch<TUser>(`/api/users/id/${user}`, {
59
+ method: "GET",
60
+ });
61
+
62
+ currentUser.value = _user;
63
+ return _user;
64
+ } catch (error) {
65
+ console.log("Error fetching current user:", error);
66
+ }
67
+ }
68
+
69
+ async function forgotPassword(email: string) {
70
+ if (!email) {
71
+ throw new Error("Email is required for password reset request.");
72
+ }
73
+
74
+ try {
75
+ const response = await $fetch("/api/auth/forget-password", {
76
+ method: "POST",
77
+ body: JSON.stringify({ email }),
78
+ headers: { "Content-Type": "application/json" },
79
+ });
80
+ return response;
81
+ } catch (error) {
82
+ console.error("Error in password reset request:", error);
83
+ throw error;
84
+ }
85
+ }
86
+
87
+ async function resetPassword(
88
+ otp: string,
89
+ newPassword: string,
90
+ passwordConfirmation: string
91
+ ) {
92
+ try {
93
+ return await $fetch("/api/auth/reset-password", {
94
+ method: "POST",
95
+ body: JSON.stringify({ otp, newPassword, passwordConfirmation }),
96
+ headers: { "Content-Type": "application/json" },
97
+ });
98
+ } catch (error) {
99
+ console.error("Error resetting password:", error);
100
+ throw error;
101
+ }
102
+ }
103
+
104
+ function verify(id: string) {
105
+ return $fetch<TKeyValuePair>(`/api/auth/verify/${id}`, {
106
+ method: "GET",
107
+ });
108
+ }
109
+
110
+ function signUp(email: string, referral: string) {
111
+ return $fetch<TKeyValuePair>("/api/auth/sign-up", {
112
+ method: "POST",
113
+ body: { email, referral },
114
+ });
115
+ }
116
+
117
+ return {
118
+ currentUser,
119
+ membership,
120
+ permissions,
121
+ hasPermission,
122
+ authenticate,
123
+ login,
124
+ logout,
125
+ getCurrentUser,
126
+ forgotPassword,
127
+ resetPassword,
128
+ verify,
129
+ signUp,
130
+ };
131
+ }
@@ -0,0 +1,34 @@
1
+ export default function useLocal() {
2
+ const { DOMAIN } = useRuntimeConfig().public;
3
+
4
+ const cookieConfig = {
5
+ domain: DOMAIN ?? "localhost",
6
+ secure: true,
7
+ maxAge: 30 * 24 * 60 * 60,
8
+ };
9
+
10
+ const drawer = useState("drawer", () => true);
11
+
12
+ const apps = computed(() => {
13
+ return [];
14
+ });
15
+
16
+ function redirect(link: string, page?: string) {
17
+ const href = page ? `${link}/${page}` : link;
18
+
19
+ window.location.href = href;
20
+ }
21
+
22
+ const headerSearch = useState("headerSearch", () => "");
23
+
24
+ const landingPage = useCookie("landingPage", cookieConfig);
25
+
26
+ return {
27
+ cookieConfig,
28
+ drawer,
29
+ apps,
30
+ redirect,
31
+ headerSearch,
32
+ landingPage,
33
+ };
34
+ }