@goweekdays/layer-common 0.0.4 → 0.0.7

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # @goweekdays/layer-common
2
2
 
3
+ ## 0.0.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 3b7164b: add promo code
8
+
9
+ ## 0.0.6
10
+
11
+ ### Patch Changes
12
+
13
+ - 01873f7: Add error page
14
+
15
+ ## 0.0.5
16
+
17
+ ### Patch Changes
18
+
19
+ - 0a6bba5: Enhance usePaymentMethod
20
+
3
21
  ## 0.0.4
4
22
 
5
23
  ### Patch Changes
@@ -0,0 +1,123 @@
1
+ <script setup lang="ts">
2
+ import { defineModel, defineProps, computed, ref } from "vue";
3
+
4
+ const props = defineProps<{
5
+ rules?: ((value: string) => boolean | string)[]; // Accepts external validation rules
6
+ }>();
7
+
8
+ const dateValue = defineModel<string>({ default: "" });
9
+ const inputRef = ref<HTMLInputElement | null>(null);
10
+
11
+ const formatDate = (event: Event) => {
12
+ const input = event.target as HTMLInputElement;
13
+ let value = input.value.replace(/\D/g, ""); // Remove non-numeric characters
14
+
15
+ // Format as MM/DD/YYYY
16
+ let formattedValue = value
17
+ .slice(0, 8)
18
+ .replace(/(\d{2})(\d{0,2})?(\d{0,4})?/, (_, m, d, y) =>
19
+ [m, d, y].filter(Boolean).join("/")
20
+ );
21
+
22
+ // Preserve cursor position
23
+ const cursorPosition = input.selectionStart ?? 0;
24
+ const slashCountBefore = (dateValue.value.match(/\//g) || []).length;
25
+ const slashCountAfter = (formattedValue.match(/\//g) || []).length;
26
+ const cursorOffset = slashCountAfter - slashCountBefore;
27
+
28
+ // Only update if value changed to prevent unnecessary reactivity updates
29
+ if (dateValue.value !== formattedValue) {
30
+ dateValue.value = formattedValue;
31
+ setTimeout(() => {
32
+ input.setSelectionRange(
33
+ cursorPosition + cursorOffset,
34
+ cursorPosition + cursorOffset
35
+ );
36
+ });
37
+ }
38
+ };
39
+
40
+ // Compute combined validation rules
41
+ const computedRules = computed(() => {
42
+ return props.rules ? [...props.rules] : [];
43
+ });
44
+
45
+ // Handle arrow key increments with cursor preservation
46
+ const handleArrowKeys = (event: KeyboardEvent) => {
47
+ if (!dateValue.value) return;
48
+
49
+ const input = event.target as HTMLInputElement;
50
+ const cursorPosition = input.selectionStart ?? 0; // Store cursor position
51
+ dateValue.value.split("/").map(Number);
52
+
53
+ let updatedDate = dateValue.value;
54
+
55
+ // Determine which part to modify
56
+ if (cursorPosition <= 2) {
57
+ updatedDate = modifyDatePart(
58
+ dateValue.value,
59
+ "month",
60
+ event.key === "ArrowUp" ? 1 : -1
61
+ );
62
+ } else if (cursorPosition <= 5) {
63
+ updatedDate = modifyDatePart(
64
+ dateValue.value,
65
+ "day",
66
+ event.key === "ArrowUp" ? 1 : -1
67
+ );
68
+ } else {
69
+ updatedDate = modifyDatePart(
70
+ dateValue.value,
71
+ "year",
72
+ event.key === "ArrowUp" ? 1 : -1
73
+ );
74
+ }
75
+
76
+ if (dateValue.value !== updatedDate) {
77
+ dateValue.value = updatedDate;
78
+ setTimeout(() => {
79
+ input.setSelectionRange(cursorPosition, cursorPosition);
80
+ });
81
+ }
82
+
83
+ event.preventDefault();
84
+ };
85
+
86
+ const modifyDatePart = (
87
+ date: string,
88
+ part: "month" | "day" | "year",
89
+ change: number
90
+ ) => {
91
+ let [month, day, year] = date.split("/").map(Number);
92
+
93
+ if (part === "month") {
94
+ month = Math.max(1, Math.min(12, month + change));
95
+ const maxDays = new Date(year, month, 0).getDate();
96
+ day = Math.min(day, maxDays); // Adjust day to fit new month's max days
97
+ } else if (part === "day") {
98
+ const maxDays = new Date(year, month, 0).getDate();
99
+ day = Math.max(1, Math.min(maxDays, day + change));
100
+ } else if (part === "year") {
101
+ year += change;
102
+ const maxDays = new Date(year, month, 0).getDate();
103
+ day = Math.min(day, maxDays); // Adjust day to fit new year's month
104
+ }
105
+
106
+ return `${String(month).padStart(2, "0")}/${String(day).padStart(
107
+ 2,
108
+ "0"
109
+ )}/${year}`;
110
+ };
111
+ </script>
112
+
113
+ <template>
114
+ <v-text-field
115
+ ref="inputRef"
116
+ v-model="dateValue"
117
+ placeholder="MM/DD/YYYY"
118
+ @input="formatDate"
119
+ @keydown.up="handleArrowKeys"
120
+ @keydown.down="handleArrowKeys"
121
+ :rules="computedRules"
122
+ ></v-text-field>
123
+ </template>
@@ -1,18 +1,29 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, nextTick, useAttrs } from "vue";
3
3
 
4
+ const props = defineProps({
5
+ infinityEnabled: { type: Boolean, default: false }, // Enable ∞ display for 0
6
+ });
7
+
4
8
  const modelValue = defineModel<number>(); // Store actual number
5
- const inputRef = ref<HTMLInputElement | null>(null); // Reference input field
6
- const attrs = useAttrs(); // Get all passed attributes (rules, required, etc.)
9
+ const inputRef = ref<HTMLInputElement | null>(null);
10
+ const attrs = useAttrs();
7
11
 
8
- let cursorPosition = 0; // Track cursor position
9
- let forceCursorToEnd = false; // Track if cursor should be forced to end
12
+ let cursorPosition = 0;
13
+ let forceCursorToEnd = false;
10
14
 
11
- // Computed property to format value with commas
15
+ // Computed property to format value with commas or infinity sign
12
16
  const formattedValue = computed({
13
- get: () =>
14
- modelValue.value !== undefined ? modelValue.value.toLocaleString() : "",
17
+ get: () => {
18
+ if (props.infinityEnabled && modelValue.value === 0) return ""; // Show ∞ if enabled
19
+ return modelValue.value?.toLocaleString() ?? "";
20
+ },
15
21
  set: (val: string) => {
22
+ if (props.infinityEnabled && val === "∞") {
23
+ modelValue.value = 0; // Convert back to 0 internally
24
+ return;
25
+ }
26
+
16
27
  const rawValue = val.replace(/\D/g, ""); // Remove non-numeric characters
17
28
  const numericValue = rawValue ? Number(rawValue) : 0;
18
29
 
@@ -30,7 +41,6 @@ const handleKeyDown = (event: KeyboardEvent) => {
30
41
  const { selectionStart, selectionEnd, value } = inputRef.value;
31
42
  const isAllSelected = selectionStart === 0 && selectionEnd === value.length;
32
43
 
33
- // Allow only numbers (prevent non-numeric input)
34
44
  if (
35
45
  !/^\d$/.test(event.key) &&
36
46
  ![
@@ -46,7 +56,6 @@ const handleKeyDown = (event: KeyboardEvent) => {
46
56
  return;
47
57
  }
48
58
 
49
- // If all text is selected and a number is pressed, replace entire value
50
59
  if (isAllSelected && /^\d$/.test(event.key)) {
51
60
  event.preventDefault();
52
61
  modelValue.value = Number(event.key);
@@ -55,13 +64,11 @@ const handleKeyDown = (event: KeyboardEvent) => {
55
64
  return;
56
65
  }
57
66
 
58
- // Allow cursor movement with ArrowLeft & ArrowRight
59
67
  if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
60
68
  forceCursorToEnd = false;
61
69
  return;
62
70
  }
63
71
 
64
- // Force cursor to end when incrementing/decrementing
65
72
  if (event.key === "ArrowUp") {
66
73
  event.preventDefault();
67
74
  modelValue.value += 1;
@@ -71,21 +78,21 @@ const handleKeyDown = (event: KeyboardEvent) => {
71
78
  modelValue.value = Math.max(0, modelValue.value - 1);
72
79
  forceCursorToEnd = true;
73
80
  } else {
74
- cursorPosition = selectionStart || 0; // Store cursor position before input
81
+ cursorPosition = selectionStart || 0;
75
82
  }
76
83
 
77
84
  nextTick(() => restoreCursor());
78
85
  };
79
86
 
80
- // Restore cursor position correctly
87
+ // Restore cursor position
81
88
  const restoreCursor = () => {
82
89
  if (!inputRef.value) return;
83
90
  const length = formattedValue.value.length;
84
91
 
85
92
  if (forceCursorToEnd) {
86
- inputRef.value.setSelectionRange(length, length); // Keep cursor at end
93
+ inputRef.value.setSelectionRange(length, length);
87
94
  } else {
88
- inputRef.value.setSelectionRange(cursorPosition, cursorPosition); // Keep normal position
95
+ inputRef.value.setSelectionRange(cursorPosition, cursorPosition);
89
96
  }
90
97
  };
91
98
  </script>
@@ -96,7 +103,22 @@ const restoreCursor = () => {
96
103
  v-model="formattedValue"
97
104
  v-bind="attrs"
98
105
  type="text"
99
- placeholder="Enter a number"
100
106
  @keydown="handleKeyDown"
101
- ></v-text-field>
107
+ >
108
+ <template v-if="$slots.prepend" v-slot:prepend>
109
+ <slot name="prepend"></slot>
110
+ </template>
111
+
112
+ <template v-if="$slots.append" v-slot:append>
113
+ <slot name="append"></slot>
114
+ </template>
115
+
116
+ <template v-if="$slots['append-inner']" v-slot:append-inner>
117
+ <slot name="append-inner"></slot>
118
+ </template>
119
+
120
+ <template v-if="$slots['prepend-inner']" v-slot:prepend-inner>
121
+ <slot name="prepend-inner"></slot>
122
+ </template>
123
+ </v-text-field>
102
124
  </template>
@@ -112,7 +112,9 @@
112
112
  height="42"
113
113
  ></v-img>
114
114
 
115
- <span v-else class="text-h5">{{ getNameInitials(name) }}</span>
115
+ <span v-else class="text-subtitle-1">{{
116
+ getNameInitials(name)
117
+ }}</span>
116
118
  </v-avatar>
117
119
  </v-btn>
118
120
  </template>
@@ -12,7 +12,7 @@
12
12
  >
13
13
  <template #activator="{ props }">
14
14
  <v-btn variant="text" class="text-none ml-2" v-bind="props">
15
- <div class="d-block text-truncate" style="max-width: 130px">
15
+ <div class="d-block text-truncate" style="width: 130px">
16
16
  {{ selectedOrg?.text || "Select organization" }}
17
17
  </div>
18
18
  <v-icon class="ml-1">mdi-menu-down</v-icon>
@@ -0,0 +1,22 @@
1
+ export default function useOrder() {
2
+ function getOrders({
3
+ search = "",
4
+ id = "",
5
+ page = 1,
6
+ status = "succeeded",
7
+ } = {}) {
8
+ return useNuxtApp().$api<Record<string, any>>("/api/orders", {
9
+ method: "GET",
10
+ query: {
11
+ search,
12
+ id,
13
+ page,
14
+ status,
15
+ },
16
+ });
17
+ }
18
+
19
+ return {
20
+ getOrders,
21
+ };
22
+ }
@@ -7,7 +7,7 @@ export default function usePaymentMethod() {
7
7
  success_return_url = "",
8
8
  failure_return_url = "",
9
9
  cancel_return_url = "",
10
- subscriptionData = {},
10
+ category = "individual",
11
11
  } = {}) {
12
12
  return useNuxtApp().$api<Record<string, any>>(
13
13
  "/api/payment-methods/e-wallet",
@@ -21,7 +21,7 @@ export default function usePaymentMethod() {
21
21
  success_return_url,
22
22
  failure_return_url,
23
23
  cancel_return_url,
24
- subscriptionData,
24
+ category,
25
25
  },
26
26
  }
27
27
  );
@@ -81,6 +81,14 @@ export default function usePaymentMethod() {
81
81
  const cardholderName = useState("cardholderName", () => "");
82
82
  const selectedPaymentMethod = useState("selectedPaymentMethod", () => "");
83
83
 
84
+ function reset() {
85
+ eWalletNumber.value = "";
86
+ cardNumber.value = "";
87
+ cardExpiration.value = "";
88
+ cardSecurityCode.value = "";
89
+ cardholderName.value = "";
90
+ }
91
+
84
92
  return {
85
93
  linkEWallet,
86
94
  linkCard,
@@ -92,5 +100,6 @@ export default function usePaymentMethod() {
92
100
  cardSecurityCode,
93
101
  cardholderName,
94
102
  selectedPaymentMethod,
103
+ reset,
95
104
  };
96
105
  }
@@ -0,0 +1,34 @@
1
+ export default function usePromoCode() {
2
+ function add(value: TPromoCode) {
3
+ return useNuxtApp().$api<Record<string, any>>("/api/promo-codes", {
4
+ method: "POST",
5
+ body: value,
6
+ });
7
+ }
8
+
9
+ function getPromoCodes({ search = "", page = 1, status = "active" } = {}) {
10
+ return useNuxtApp().$api<Record<string, any>>("/api/promo-codes", {
11
+ method: "GET",
12
+ query: {
13
+ search,
14
+ page,
15
+ status,
16
+ },
17
+ });
18
+ }
19
+
20
+ function getByCode(code: string) {
21
+ return useNuxtApp().$api<Record<string, any>>(
22
+ `/api/promo-codes/code/${code}`,
23
+ {
24
+ method: "GET",
25
+ }
26
+ );
27
+ }
28
+
29
+ return {
30
+ add,
31
+ getPromoCodes,
32
+ getByCode,
33
+ };
34
+ }
@@ -13,6 +13,18 @@ export default function useSubscription() {
13
13
  return useNuxtApp().$api<TSubscription>(`/api/subscriptions/id/${id}`);
14
14
  }
15
15
 
16
+ function getByAffiliateId(id: string) {
17
+ return useNuxtApp().$api<TSubscription>(
18
+ `/api/subscriptions/affiliate/${id}`
19
+ );
20
+ }
21
+
22
+ function getByOrgId(id: string) {
23
+ return useNuxtApp().$api<Record<string, any>>(
24
+ `/api/subscriptions/org/${id}`
25
+ );
26
+ }
27
+
16
28
  function getSubscriptions() {
17
29
  return useNuxtApp().$api("/api/subscriptions");
18
30
  }
@@ -34,61 +46,84 @@ export default function useSubscription() {
34
46
  () => "inactive"
35
47
  );
36
48
 
37
- async function checkSubscription() {
49
+ async function affSubscriptionStatus() {
38
50
  const { currentUser } = useLocalAuth();
39
51
 
40
52
  if (currentUser.value && currentUser.value._id) {
41
53
  try {
42
- const result = await getSubscriptionStatusById(currentUser.value._id);
43
- affiliateSubscription.value = result.status;
54
+ const result = await getByAffiliateId(currentUser.value._id);
55
+ affiliateSubscription.value = result.status as string;
44
56
  } catch (error) {
45
57
  console.error("failed to get the subscription", error);
46
58
  }
47
59
  }
48
60
  }
49
61
 
50
- function initAffiliateSubscription(
51
- customer_id: string,
52
- amount: number,
53
- payment_method_id: string,
54
- currency?: string
55
- ) {
56
- return useNuxtApp().$api("/api/subscriptions/affiliate", {
57
- method: "POST",
58
- body: {
59
- customer_id,
60
- amount,
61
- payment_method_id,
62
- currency,
63
- },
64
- });
62
+ const orgSubscription = useState("orgSubscription", () => "inactive");
63
+
64
+ async function orgSubscriptionStatus() {
65
+ const { currentOrg } = useOrg();
66
+
67
+ if (currentOrg.value) {
68
+ try {
69
+ const result = await getByOrgId(currentOrg.value);
70
+ orgSubscription.value = result.status as string;
71
+ } catch (error) {
72
+ console.error("failed to get the subscription", error);
73
+ }
74
+ }
65
75
  }
66
76
 
67
- type TOrgSubscription = {
77
+ type TSub = {
68
78
  customer_id: string;
69
79
  amount: number;
70
80
  payment_method_id: string;
81
+ payment_method_type: string;
82
+ payment_method_channel: string;
83
+ payment_method_number: string;
84
+ payment_method_expiry_month?: string;
85
+ payment_method_expiry_year?: string;
86
+ payment_method_cvv?: string;
87
+ payment_method_cardholder_name?: string;
71
88
  currency?: string;
72
- organization: TOrg;
89
+ seats?: number;
90
+ organization?: TOrg;
73
91
  billingAddress: TAddress;
74
92
  };
75
93
 
76
- function initOrgSubscription(value: TOrgSubscription) {
77
- return useNuxtApp().$api("/api/subscriptions/organization", {
78
- method: "POST",
79
- body: value,
80
- });
94
+ function initOrgSubscription(value: TSub) {
95
+ return useNuxtApp().$api<Record<string, any>>(
96
+ "/api/subscriptions/organization",
97
+ {
98
+ method: "POST",
99
+ body: value,
100
+ }
101
+ );
102
+ }
103
+
104
+ function initAffiliateSubscription(value: TSub) {
105
+ return useNuxtApp().$api<Record<string, any>>(
106
+ "/api/subscriptions/affiliate",
107
+ {
108
+ method: "POST",
109
+ body: value,
110
+ }
111
+ );
81
112
  }
82
113
 
83
114
  return {
84
115
  add,
85
116
  getById,
117
+ getByOrgId,
86
118
  getSubscriptions,
119
+ affSubscriptionStatus,
87
120
  affiliateSubscription,
88
- checkSubscription,
121
+ orgSubscriptionStatus,
122
+ orgSubscription,
89
123
  getSubscriptionStatusById,
90
124
  cancelSubscription,
91
125
  initAffiliateSubscription,
92
126
  initOrgSubscription,
127
+ getByAffiliateId,
93
128
  };
94
129
  }
@@ -6,6 +6,31 @@ export default function useUtils() {
6
6
  return regex.test(v) || "Please enter a valid email address";
7
7
  };
8
8
 
9
+ const validateDate = (value: string): boolean | string => {
10
+ const dateRegex = /^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$/;
11
+
12
+ if (!dateRegex.test(value)) return "Invalid date format (MM/DD/YYYY)";
13
+
14
+ return true;
15
+ };
16
+
17
+ const minOneMonthAdvance = (value: string): boolean | string => {
18
+ if (!/^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$/.test(value)) {
19
+ return "Invalid date format (MM/DD/YYYY)";
20
+ }
21
+
22
+ const [month, day, year] = value.split("/").map(Number);
23
+ const selectedDate = new Date(year, month - 1, day);
24
+
25
+ const currentDate = new Date();
26
+ const minDate = new Date();
27
+ minDate.setMonth(currentDate.getMonth() + 1); // 1 month in advance
28
+
29
+ return (
30
+ selectedDate >= minDate || "Date must be at least 1 month in advance"
31
+ );
32
+ };
33
+
9
34
  const passwordRule = (v: string) =>
10
35
  v.length >= 8 || "Password must be at least 8 characters long";
11
36
 
@@ -131,6 +156,39 @@ export default function useUtils() {
131
156
  maximumFractionDigits: 2,
132
157
  });
133
158
 
159
+ function formatNumber(
160
+ amount: number,
161
+ options: {
162
+ currency?: string;
163
+ locale?: string;
164
+ useSymbol?: boolean;
165
+ decimalPlaces?: number;
166
+ } = {}
167
+ ): string {
168
+ const {
169
+ currency,
170
+ locale = "en-US",
171
+ useSymbol = false,
172
+ decimalPlaces = 2,
173
+ } = options;
174
+
175
+ return new Intl.NumberFormat(locale, {
176
+ style: useSymbol && currency ? "currency" : "decimal",
177
+ currency: currency || "USD", // Default currency (ignored if `useSymbol` is false)
178
+ minimumFractionDigits: decimalPlaces,
179
+ maximumFractionDigits: decimalPlaces,
180
+ }).format(amount);
181
+ }
182
+
183
+ // 🔹 Examples:
184
+ // console.log(formatNumber(1234.56)); // "1,234.56" (comma separator, 2 decimals)
185
+ // console.log(formatNumber(1234.56, { decimalPlaces: 0 })); // "1,234" (no decimals)
186
+ // console.log(formatNumber(1234.56, { useSymbol: true, currency: "USD" })); // "$1,234.56"
187
+ // console.log(formatNumber(1234.56, { useSymbol: true, currency: "PHP", decimalPlaces: 0 })); // "₱1,234"
188
+ // console.log(formatNumber(1234.56, { useSymbol: false, decimalPlaces: 0 })); // "1,234"
189
+ // console.log(formatNumber(1234.56, { useSymbol: true, currency: "EUR", locale: "de-DE" })); // "1.234,56 €"
190
+ // console.log(formatNumber(1234.56, { useSymbol: true, currency: "EUR", locale: "de-DE", decimalPlaces: 0 })); // "1.234 €"
191
+
134
192
  return {
135
193
  requiredRule,
136
194
  emailRule,
@@ -146,5 +204,8 @@ export default function useUtils() {
146
204
  validateWord,
147
205
  getCountries,
148
206
  formatter,
207
+ formatNumber,
208
+ validateDate,
209
+ minOneMonthAdvance,
149
210
  };
150
211
  }
package/error.vue ADDED
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ import type { NuxtError } from "#app";
3
+
4
+ const props = defineProps({
5
+ error: Object as () => NuxtError,
6
+ });
7
+ </script>
8
+
9
+ <template>
10
+ <v-app>
11
+ <v-main>
12
+ <v-row
13
+ class="fill-height"
14
+ no-gutters
15
+ justify="center"
16
+ align-content="center"
17
+ >
18
+ <v-col cols="9">
19
+ <v-row>
20
+ <v-col cols="12" class="text-center text-h2">
21
+ <span class="font-weight-bold">{{ props.error?.message }}</span>
22
+ </v-col>
23
+ <v-col cols="12">
24
+ <v-row no-gutters justify="center">
25
+ <v-btn
26
+ rounded="xl"
27
+ size="large"
28
+ class="text-none"
29
+ variant="tonal"
30
+ :to="{ name: 'index' }"
31
+ >
32
+ Go to home page
33
+ </v-btn>
34
+ </v-row>
35
+ </v-col>
36
+ </v-row>
37
+ </v-col>
38
+ </v-row>
39
+ </v-main>
40
+ </v-app>
41
+ </template>
@@ -0,0 +1,7 @@
1
+ <template>
2
+ <v-app>
3
+ <v-main>
4
+ <slot />
5
+ </v-main>
6
+ </v-app>
7
+ </template>
@@ -0,0 +1,25 @@
1
+ export default defineNuxtRouteMiddleware(async () => {
2
+ // Ensure middleware runs only on the client side
3
+ if (import.meta.server) return;
4
+
5
+ const { cookieConfig } = useRuntimeConfig().public;
6
+
7
+ // Get access token from cookies
8
+ const accessToken = useCookie("accessToken", cookieConfig).value;
9
+
10
+ if (!accessToken) {
11
+ // Redirect to login page if no access token
12
+ return navigateTo({ name: "index" });
13
+ }
14
+
15
+ const { getCurrentUser } = useLocalAuth();
16
+
17
+ try {
18
+ await getCurrentUser();
19
+ } catch (error) {
20
+ console.log(error);
21
+
22
+ // Redirect to login page if user authentication fails
23
+ return navigateTo({ name: "index" });
24
+ }
25
+ });
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+
3
+ const hexSchema = z
4
+ .string()
5
+ .regex(/^[0-9a-fA-F]{24}$/, "Invalid organization ID");
6
+
7
+ export default defineNuxtRouteMiddleware((to) => {
8
+ const { organization } = to.params;
9
+
10
+ if (organization && !hexSchema.safeParse(organization).success) {
11
+ return navigateTo(
12
+ { name: "require-organization-membership" },
13
+ { replace: true }
14
+ );
15
+ }
16
+ });
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@goweekdays/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "0.0.4",
5
+ "version": "0.0.7",
6
6
  "main": "./nuxt.config.ts",
7
7
  "publishConfig": {
8
8
  "access": "public"
@@ -28,6 +28,7 @@
28
28
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
29
29
  "dependencies": {
30
30
  "@mdi/font": "^7.4.47",
31
- "sass": "^1.80.6"
31
+ "sass": "^1.80.6",
32
+ "zod": "^3.24.2"
32
33
  }
33
34
  }
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <h1>index</h1>
3
+ </template>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <v-row no-gutters class="fill-height" justify="center" align-content="center">
3
+ <v-card class="pa-4" elevation="4" color="success lighten-1">
4
+ <v-card-title class="text-white d-flex align-center">
5
+ <v-icon class="mr-2">mdi-check-circle</v-icon>
6
+ Successfully Linked Payment Method
7
+ </v-card-title>
8
+
9
+ <v-card-actions class="d-flex justify-center">
10
+ <v-btn
11
+ color="white"
12
+ variant="tonal"
13
+ rounded="xl"
14
+ size="large"
15
+ @click="closeWindow"
16
+ >Close</v-btn
17
+ >
18
+ </v-card-actions>
19
+ </v-card>
20
+ </v-row>
21
+ </template>
22
+
23
+ <script setup>
24
+ definePageMeta({
25
+ layout: "plain",
26
+ });
27
+
28
+ const closeWindow = () => {
29
+ window.close();
30
+ };
31
+ </script>
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <v-row no-gutters class="fill-height" align="center" justify="center">
3
+ <v-col cols="12" lg="6" md="6" sm="6" class="text-center text-subtitle-1">
4
+ You must be a member of an organization to view this page. Please join or
5
+ contact your administrator for access.
6
+ <v-row no-gutters justify="center" align="center" class="mt-2">
7
+ <v-btn
8
+ @click="goToAccount"
9
+ rounded="xl"
10
+ variant="tonal"
11
+ class="text-none text-subtitle-2"
12
+ >
13
+ Go to Account Page
14
+ </v-btn>
15
+ <span class="mx-2">or</span>
16
+ <v-btn
17
+ @click="createOrg"
18
+ rounded="xl"
19
+ variant="tonal"
20
+ class="text-none text-subtitle-2"
21
+ >
22
+ Create an Organization
23
+ </v-btn>
24
+ </v-row>
25
+ </v-col>
26
+ </v-row>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ definePageMeta({
31
+ layout: "plain",
32
+ });
33
+
34
+ const { APP_ACCOUNT, APP_NAME, APP_ORG } = useRuntimeConfig().public;
35
+
36
+ function goToAccount() {
37
+ window.location.href = `${APP_ACCOUNT}/home`;
38
+ }
39
+
40
+ function createOrg() {
41
+ if (APP_NAME.toLowerCase() === "org") {
42
+ navigateTo({ name: "organizations-create" });
43
+ } else {
44
+ window.location.href = `${APP_ORG}/organizations/create`;
45
+ }
46
+ }
47
+ </script>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <v-row no-gutters class="fill-height" align="center" justify="center">
3
+ <v-col cols="12" lg="6" md="6" sm="6" class="text-center text-subtitle-1">
4
+ You must be a member of an organization to view this page. Please join or
5
+ contact your administrator for access.
6
+ <v-row no-gutters justify="center" class="mt-2">
7
+ <v-btn
8
+ @click="goToAccount"
9
+ rounded="xl"
10
+ variant="tonal"
11
+ class="text-none text-subtitle-2"
12
+ >
13
+ Go to Account Page
14
+ </v-btn>
15
+ </v-row>
16
+ </v-col>
17
+ </v-row>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ definePageMeta({
22
+ layout: "plain",
23
+ });
24
+
25
+ function goToAccount() {
26
+ const { APP_ACCOUNT } = useRuntimeConfig().public;
27
+ window.location.href = `${APP_ACCOUNT}/home`;
28
+ }
29
+ </script>
Binary file
@@ -0,0 +1,19 @@
1
+ declare type TPromoTier = {
2
+ min: number;
3
+ max: number;
4
+ price: number;
5
+ };
6
+
7
+ declare type TPromoCode = {
8
+ _id?: ObjectId;
9
+ code: string;
10
+ description?: string;
11
+ type: "tiered" | "fixed";
12
+ tiers?: TPromoTier[];
13
+ fixed_rate?: number;
14
+ appliesTo?: string; // "subscription" | "order"
15
+ createdAt?: string; // Date of creation
16
+ expiresAt?: string; // Optional expiry
17
+ assignedTo?: string | ObjectId; // Store the only ape who used it
18
+ status?: "active" | "expired" | "disabled";
19
+ };