@goweekdays/layer-common 1.4.6 → 1.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @goweekdays/layer-common
2
2
 
3
+ ## 1.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fab8c5c: Add promo code support and input components
8
+
3
9
  ## 1.4.6
4
10
 
5
11
  ### Patch Changes
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <v-text-field
3
+ type="text"
4
+ inputmode="numeric"
5
+ :model-value="display"
6
+ @update:model-value="onInput"
7
+ @blur="format"
8
+ @keydown="onKeydown"
9
+ v-bind="$attrs"
10
+ >
11
+ <!-- forward all slots -->
12
+ <template v-for="(_, name) in $slots" #[name]="slotProps">
13
+ <slot :name="name" v-bind="slotProps" />
14
+ </template>
15
+ </v-text-field>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ /**
20
+ * numeric v-model
21
+ */
22
+
23
+ const localProps = defineProps({
24
+ step: {
25
+ type: Number,
26
+ default: 1,
27
+ },
28
+ });
29
+ const model = defineModel<number>({ default: 0 });
30
+
31
+ const display = ref<string>("");
32
+
33
+ const parse = (v: string) => Number(v.replace(/[^0-9]/g, "") || 0);
34
+
35
+ const format = () => {
36
+ display.value = model.value.toLocaleString("en-US");
37
+ };
38
+
39
+ function onInput(val: string) {
40
+ if (!/^[0-9,]*$/.test(val)) return;
41
+ model.value = parse(val);
42
+ display.value = val;
43
+ }
44
+
45
+ function onKeydown(e: KeyboardEvent) {
46
+ if (e.key === "ArrowUp") {
47
+ e.preventDefault();
48
+ model.value += localProps.step;
49
+ format();
50
+ }
51
+
52
+ if (e.key === "ArrowDown") {
53
+ e.preventDefault();
54
+ model.value = Math.max(0, model.value - localProps.step);
55
+ format();
56
+ }
57
+ }
58
+
59
+ watch(() => model.value, format, { immediate: true });
60
+ </script>
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <v-text-field
3
+ type="text"
4
+ inputmode="numeric"
5
+ :model-value="display"
6
+ @update:model-value="onInput"
7
+ @update:focused="doAutoCorrect"
8
+ @keydown="onKeydown"
9
+ v-bind="$attrs"
10
+ >
11
+ <!-- forward all slots -->
12
+ <template v-for="(_, name) in $slots" #[name]="slotProps">
13
+ <slot :name="name" v-bind="slotProps" />
14
+ </template>
15
+ </v-text-field>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ /**
20
+ * Numeric input that displays ∞ when value is 0
21
+ */
22
+
23
+ const localProps = defineProps({
24
+ step: {
25
+ type: Number,
26
+ default: 1,
27
+ },
28
+ minValue: {
29
+ type: Number,
30
+ default: 0,
31
+ },
32
+ autoCorrect: {
33
+ type: Boolean,
34
+ default: false,
35
+ },
36
+ });
37
+
38
+ const model = defineModel<number>({ default: 0 });
39
+
40
+ const display = ref<string>("");
41
+
42
+ const INFINITY_SYMBOL = "∞";
43
+
44
+ const parse = (v: string) => {
45
+ if (v === INFINITY_SYMBOL || v === "") return 0;
46
+ return Number(v.replace(/[^0-9]/g, "") || 0);
47
+ };
48
+
49
+ const format = () => {
50
+ display.value =
51
+ model.value === 0 ? INFINITY_SYMBOL : model.value.toLocaleString("en-US");
52
+ };
53
+
54
+ function doAutoCorrect() {
55
+ if (!localProps.autoCorrect) return;
56
+
57
+ // Auto-correct if value is invalid (> 0 but <= minValue)
58
+ if (model.value > 0 && model.value <= localProps.minValue) {
59
+ model.value = localProps.minValue + 1;
60
+ format();
61
+ }
62
+ }
63
+
64
+ function onInput(val: string) {
65
+ // Allow infinity symbol, empty, or numeric input
66
+ if (val === INFINITY_SYMBOL || val === "") {
67
+ model.value = 0;
68
+ display.value = val;
69
+ return;
70
+ }
71
+
72
+ // If current display is infinity and user types a number, replace entirely
73
+ if (display.value === INFINITY_SYMBOL) {
74
+ const numericOnly = val.replace(/[^0-9]/g, "");
75
+ if (numericOnly) {
76
+ model.value = Number(numericOnly);
77
+ display.value = numericOnly;
78
+ return;
79
+ }
80
+ }
81
+
82
+ if (!/^[0-9,]*$/.test(val)) return;
83
+ model.value = parse(val);
84
+ display.value = val;
85
+ }
86
+
87
+ function onKeydown(e: KeyboardEvent) {
88
+ if (e.key === "Enter") {
89
+ doAutoCorrect();
90
+ }
91
+
92
+ if (e.key === "ArrowUp") {
93
+ e.preventDefault();
94
+ // If value is 0 (infinity), start from minValue + 1
95
+ if (model.value === 0) {
96
+ model.value = localProps.minValue + 1;
97
+ } else {
98
+ model.value += localProps.step;
99
+ }
100
+ format();
101
+ }
102
+
103
+ if (e.key === "ArrowDown") {
104
+ e.preventDefault();
105
+ // Don't go below 0
106
+ model.value = Math.max(0, model.value - localProps.step);
107
+ format();
108
+ }
109
+ }
110
+
111
+ watch(() => model.value, format, { immediate: true });
112
+ </script>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <v-text-field
3
+ type="text"
4
+ :model-value="display"
5
+ @update:model-value="onInput"
6
+ @blur="format"
7
+ @keydown="onKeydown"
8
+ v-bind="$attrs"
9
+ >
10
+ <!-- forward all slots -->
11
+ <template v-for="(_, name) in $slots" #[name]="slotProps">
12
+ <slot :name="name" v-bind="slotProps" />
13
+ </template>
14
+ </v-text-field>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ /**
19
+ * Input that converts spaces to hyphens and forces lowercase, useful for slugs, codes, or identifiers
20
+ */
21
+
22
+ const model = defineModel<string>({ default: "" });
23
+
24
+ const display = ref<string>("");
25
+
26
+ const transform = (v: string) => {
27
+ return v.replace(/\s+/g, "-").toLowerCase();
28
+ };
29
+
30
+ const format = () => {
31
+ display.value = transform(model.value);
32
+ model.value = display.value;
33
+ };
34
+
35
+ function onInput(val: string) {
36
+ const transformed = transform(val);
37
+ model.value = transformed;
38
+ display.value = transformed;
39
+ }
40
+
41
+ function onKeydown(e: KeyboardEvent) {
42
+ if (e.key === " ") {
43
+ e.preventDefault();
44
+ model.value = transform(display.value + "-");
45
+ display.value = model.value;
46
+ }
47
+ }
48
+
49
+ watch(() => model.value, format, { immediate: true });
50
+ </script>
@@ -66,7 +66,6 @@
66
66
  <v-dialog v-model="dialogView" width="450" persistent>
67
67
  <MemberInvite
68
68
  title="Invite Details"
69
- :app="props.app"
70
69
  v-model="invitation"
71
70
  @close="setInvite({ dialog: false })"
72
71
  @cancel:invite="handleOpenCancelInvite()"
@@ -141,6 +140,8 @@ const pageRange = ref("-- - -- of --");
141
140
  const items = ref<Array<Record<string, any>>>([]);
142
141
  const { headerSearch } = useLocal();
143
142
 
143
+ const isOrg = computed(() => props.app === "org");
144
+
144
145
  const {
145
146
  data: getInviteReq,
146
147
  refresh: getInvitations,
@@ -154,6 +155,7 @@ const {
154
155
  status: props.status,
155
156
  type: "user-invite,member-invite",
156
157
  org: props.org,
158
+ app: isOrg.value ? "" : props.app,
157
159
  })
158
160
  );
159
161
 
@@ -179,9 +181,7 @@ function setInvite({
179
181
  } = {}) {
180
182
  Object.assign(invitation.value, JSON.parse(JSON.stringify(data)));
181
183
 
182
- if (dialog) {
183
- dialogView.value = dialog;
184
- }
184
+ dialogView.value = dialog;
185
185
  }
186
186
 
187
187
  function tableRowClickHandler(_: any, data: any) {
@@ -26,6 +26,7 @@
26
26
  item-value="code"
27
27
  :rules="isMutable ? [requiredRule] : []"
28
28
  :loading="loadingApps"
29
+ :readonly="!isMutable"
29
30
  ></v-select>
30
31
  </v-col>
31
32
  </v-row>
@@ -189,7 +190,9 @@ const invite = defineModel<TMemberInvitation>({
189
190
  default: () => useMember().invitation.value,
190
191
  });
191
192
 
192
- invite.value.app = localProps.app;
193
+ if (localProps.mode === "add") {
194
+ invite.value.app = localProps.app;
195
+ }
193
196
 
194
197
  const message = defineModel("message", { default: "" });
195
198
 
@@ -54,6 +54,7 @@ export default function useOrg() {
54
54
  contact: "",
55
55
  email: "",
56
56
  createdBy: "",
57
+ promoCode: "",
57
58
  });
58
59
 
59
60
  return {
@@ -0,0 +1,78 @@
1
+ export default function usePromo() {
2
+ const promo = ref<TPromo>({
3
+ _id: "",
4
+ code: "",
5
+ type: "fixed",
6
+ flatRate: 0,
7
+ fixedRate: 0,
8
+ tiers: [],
9
+ currency: "",
10
+ startDate: "",
11
+ endDate: "",
12
+ seats: 0,
13
+ usage: 0,
14
+ apps: [],
15
+ });
16
+
17
+ function add(value: TPromo) {
18
+ return $fetch<Record<string, any>>("/api/promos", {
19
+ method: "POST",
20
+ body: value,
21
+ });
22
+ }
23
+
24
+ async function getAll({
25
+ page = 1,
26
+ limit = 10,
27
+ search = "",
28
+ status = "",
29
+ } = {}) {
30
+ return $fetch<Record<string, any>>("/api/promos", {
31
+ method: "GET",
32
+ query: { page, limit, search, status },
33
+ });
34
+ }
35
+
36
+ async function getByCode(code: string) {
37
+ return $fetch<Record<string, any>>(`/api/promos/code/${code}`, {
38
+ method: "GET",
39
+ });
40
+ }
41
+
42
+ async function getById(id: string) {
43
+ return $fetch<Record<string, any>>(`/api/promos/id/${id}`, {
44
+ method: "GET",
45
+ });
46
+ }
47
+
48
+ async function updateById(
49
+ id: string,
50
+ value: Pick<
51
+ TPromo,
52
+ | "code"
53
+ | "apps"
54
+ | "type"
55
+ | "flatRate"
56
+ | "fixedRate"
57
+ | "tiers"
58
+ | "currency"
59
+ | "startDate"
60
+ | "endDate"
61
+ | "seats"
62
+ | "usage"
63
+ >
64
+ ) {
65
+ return $fetch<Record<string, any>>(`/api/promos/id/${id}`, {
66
+ method: "PATCH",
67
+ body: value,
68
+ });
69
+ }
70
+
71
+ async function deleteById(id: string) {
72
+ return $fetch<Record<string, any>>(`/api/promos/${id}`, {
73
+ method: "DELETE",
74
+ });
75
+ }
76
+
77
+ return { promo, add, getAll, getByCode, getById, updateById, deleteById };
78
+ }
@@ -42,8 +42,21 @@ export default function useSubscription() {
42
42
  });
43
43
  }
44
44
 
45
+ function getAll({ page = 1, limit = 20, search = "", status = "" } = {}) {
46
+ return $fetch<Record<string, any>>(`/api/subscriptions`, {
47
+ method: "GET",
48
+ query: {
49
+ page,
50
+ limit,
51
+ search,
52
+ status,
53
+ },
54
+ });
55
+ }
56
+
45
57
  return {
46
58
  subscription,
59
+ getAll,
47
60
  getByOrg,
48
61
  getTransactionsById,
49
62
  updateSeatById,
package/nuxt.config.ts CHANGED
@@ -69,8 +69,8 @@ export default defineNuxtConfig({
69
69
  "/api/entities/**": { proxy: `${process.env.API_CORE}/api/entities/**` },
70
70
  "/api/workflows/**": { proxy: `${process.env.API_CORE}/api/workflows/**` },
71
71
  "/api/roles/**": { proxy: `${process.env.API_CORE}/api/roles/**` },
72
- "/api/promo-codes/**": {
73
- proxy: `${process.env.API_CORE}/api/promo-codes/**`,
72
+ "/api/promos/**": {
73
+ proxy: `${process.env.API_CORE}/api/promos/**`,
74
74
  },
75
75
  "/api/verifications/**": {
76
76
  proxy: `${process.env.API_CORE}/api/verifications/**`,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@goweekdays/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "1.4.6",
5
+ "version": "1.5.0",
6
6
  "main": "./nuxt.config.ts",
7
7
  "publishConfig": {
8
8
  "access": "public"
@@ -21,7 +21,7 @@
21
21
  rounded
22
22
  @click="navigateTo({ name: 'index' })"
23
23
  >
24
- Return to Landing Page
24
+ Return
25
25
  </v-btn>
26
26
  </v-row>
27
27
  </v-col>
package/types/org.d.ts CHANGED
@@ -5,6 +5,7 @@ declare type TOrg = {
5
5
  email?: string;
6
6
  contact?: string;
7
7
  createdBy: string;
8
+ promoCode?: string;
8
9
  status?: string;
9
10
  createdAt?: string;
10
11
  updatedAt?: string;
@@ -0,0 +1,22 @@
1
+ declare type TPromo = {
2
+ _id?: string;
3
+ code: string;
4
+ type: "flat" | "fixed" | "volume";
5
+ flatRate?: number; // regardless of seats
6
+ fixedRate?: number; // per seat
7
+ tiers?: Array<{
8
+ minSeats: number;
9
+ maxSeats: number;
10
+ rate: number;
11
+ }>;
12
+ currency: string;
13
+ startDate?: Date | string;
14
+ endDate?: Date | string;
15
+ seats?: number;
16
+ usage?: number;
17
+ apps?: string[];
18
+ status?: "active" | "inactive" | "expired";
19
+ createdAt?: Date | string;
20
+ updatedAt?: Date | string;
21
+ deletedAt?: Date | string;
22
+ };