unictive-angular 1.0.42 → 1.0.45
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/bin/utils/resource/bitbucket-pipelines.yml +3 -2
- package/bin/utils/resource/src/app/API/auth/auth.service.ts +1 -7
- package/bin/utils/resource/src/app/core/config/list-store.ts +209 -0
- package/bin/utils/resource/src/app/core/guards/login.guard.ts +0 -5
- package/bin/utils/resource/src/app/shared/ui/input-text/input-text.component.ts +43 -7
- package/bin/utils/updatePackageJson.js +1 -1
- package/package.json +1 -1
|
@@ -13,13 +13,14 @@ definitions:
|
|
|
13
13
|
- node
|
|
14
14
|
script:
|
|
15
15
|
- npm install -g wrangler # Install Wrangler CLI versi terbaru
|
|
16
|
-
- npm install --
|
|
16
|
+
- npm install --legacy-peer-deps # Install dependencies dari package.json di root
|
|
17
17
|
- npm run build
|
|
18
18
|
- cd workers-site
|
|
19
19
|
- npm install
|
|
20
20
|
- ls
|
|
21
21
|
- cd ../
|
|
22
|
-
- CLOUDFLARE_API_TOKEN=$CF_API_TOKEN wrangler deploy --config
|
|
22
|
+
- CLOUDFLARE_API_TOKEN=$CF_API_TOKEN wrangler deploy --config
|
|
23
|
+
wrangler.toml
|
|
23
24
|
|
|
24
25
|
clone:
|
|
25
26
|
depth: full # Clone seluruh history git (diperlukan SonarQube)
|
|
@@ -90,14 +90,8 @@ export class AuthService extends AbstractApiService<any> {
|
|
|
90
90
|
})
|
|
91
91
|
|
|
92
92
|
return Entity_User.load(async () => {
|
|
93
|
-
let isRole = null;
|
|
94
|
-
await this.helper.getStorage('role').then((res) => {
|
|
95
|
-
if (res) {
|
|
96
|
-
isRole = res;
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
93
|
|
|
100
|
-
const res = await this.get(
|
|
94
|
+
const res = await this.get('/refresh');
|
|
101
95
|
if (res) {
|
|
102
96
|
this.setLogin(true);
|
|
103
97
|
this.helper.setStorage('users', res?.data?.user);
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { signal, computed } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export interface PaginationMeta {
|
|
4
|
+
current_page: number;
|
|
5
|
+
from: number;
|
|
6
|
+
last_page: number;
|
|
7
|
+
per_page: number;
|
|
8
|
+
to: number;
|
|
9
|
+
total: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PaginatedResponse<T, M = PaginationMeta> {
|
|
13
|
+
data: T[];
|
|
14
|
+
meta: M;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface EntityState<T, M = PaginationMeta> {
|
|
18
|
+
value: T[];
|
|
19
|
+
loading: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
meta?: M;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ListStoreFactory<
|
|
25
|
+
T extends Record<any, any>,
|
|
26
|
+
M extends PaginationMeta = PaginationMeta,
|
|
27
|
+
K extends keyof T = 'id'
|
|
28
|
+
>(keyField?: K | any) {
|
|
29
|
+
|
|
30
|
+
const field = (keyField ?? ('id' as K));
|
|
31
|
+
|
|
32
|
+
const _state = signal<EntityState<T, M>>({
|
|
33
|
+
value: [],
|
|
34
|
+
loading: false,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ========================
|
|
38
|
+
// Computed Selectors
|
|
39
|
+
// ========================
|
|
40
|
+
|
|
41
|
+
const all = computed(() => _state().value);
|
|
42
|
+
const count = computed(() => _state().value.length);
|
|
43
|
+
const meta = computed(() => _state().meta);
|
|
44
|
+
const loading = computed(() => _state().loading);
|
|
45
|
+
const error = computed(() => _state().error);
|
|
46
|
+
|
|
47
|
+
const hasNextPage = computed(() => {
|
|
48
|
+
const m = _state().meta;
|
|
49
|
+
if (!m) return false;
|
|
50
|
+
return m.current_page < m.last_page;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const currentPage = computed(() => _state().meta?.current_page ?? 1);
|
|
54
|
+
|
|
55
|
+
const mapByKey = computed(() => {
|
|
56
|
+
const map = new Map<T[K], T>();
|
|
57
|
+
for (const item of _state().value) {
|
|
58
|
+
map.set(item[field], item);
|
|
59
|
+
}
|
|
60
|
+
return map;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const getByKey = (key: T[K]) =>
|
|
64
|
+
computed(() => mapByKey().get(key));
|
|
65
|
+
|
|
66
|
+
// ========================
|
|
67
|
+
// Private Helpers
|
|
68
|
+
// ========================
|
|
69
|
+
|
|
70
|
+
function deduplicate(items: T[]): T[] {
|
|
71
|
+
const map = new Map<T[K], T>();
|
|
72
|
+
for (const item of items) {
|
|
73
|
+
map.set(item[field], item);
|
|
74
|
+
}
|
|
75
|
+
return Array.from(map.values());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function setLoading(value: boolean) {
|
|
79
|
+
_state.update(s => ({ ...s, loading: value }));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function setError(message?: string) {
|
|
83
|
+
_state.update(s => ({ ...s, error: message }));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ========================
|
|
87
|
+
// Public API
|
|
88
|
+
// ========================
|
|
89
|
+
|
|
90
|
+
async function loadPage(
|
|
91
|
+
page: number,
|
|
92
|
+
fetchFn: (page: number) => Promise<PaginatedResponse<T, M>>
|
|
93
|
+
) {
|
|
94
|
+
setLoading(true);
|
|
95
|
+
setError(undefined);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetchFn(page);
|
|
99
|
+
|
|
100
|
+
console.log(response);
|
|
101
|
+
_state.update(curr => ({
|
|
102
|
+
value: page === 1
|
|
103
|
+
? response.data
|
|
104
|
+
: deduplicate([...curr.value, ...response.data]),
|
|
105
|
+
meta: response.meta,
|
|
106
|
+
loading: false,
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
return response;
|
|
110
|
+
} catch (err: any) {
|
|
111
|
+
setLoading(false);
|
|
112
|
+
setError(err?.message ?? 'Unknown error');
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- Async Loader ---
|
|
118
|
+
async function load(
|
|
119
|
+
page: number,
|
|
120
|
+
fetchFn: (page: number) => Promise<PaginatedResponse<T, M>>
|
|
121
|
+
) {
|
|
122
|
+
_state.update((curr) => ({
|
|
123
|
+
...curr,
|
|
124
|
+
loading: true,
|
|
125
|
+
error: undefined,
|
|
126
|
+
}));
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetchFn(page);
|
|
129
|
+
// console.log('Entity Store Loaded Data:', data);
|
|
130
|
+
_state.set({ value: response.data, meta: response.meta, loading: false });
|
|
131
|
+
return response.data;
|
|
132
|
+
} catch (err: any) {
|
|
133
|
+
_state.update((curr) => ({
|
|
134
|
+
...curr,
|
|
135
|
+
loading: false,
|
|
136
|
+
error: err.message ?? 'Unknown error',
|
|
137
|
+
}));
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async function loadNextPage(
|
|
145
|
+
fetchFn: (page: number) => Promise<PaginatedResponse<T, M>>
|
|
146
|
+
) {
|
|
147
|
+
if (!hasNextPage() || loading()) return;
|
|
148
|
+
|
|
149
|
+
const nextPage = currentPage() + 1;
|
|
150
|
+
return loadPage(nextPage, fetchFn);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function refresh(
|
|
154
|
+
fetchFn: (page: number) => Promise<PaginatedResponse<T, M>>
|
|
155
|
+
) {
|
|
156
|
+
return loadPage(1, fetchFn);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function reset() {
|
|
160
|
+
_state.set({
|
|
161
|
+
value: [],
|
|
162
|
+
loading: false,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function clear() {
|
|
167
|
+
reset();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function update(key: T[K], partial: Partial<T>) {
|
|
171
|
+
_state.update(curr => ({
|
|
172
|
+
...curr,
|
|
173
|
+
value: curr.value.map(i =>
|
|
174
|
+
i[field] === key ? { ...i, ...partial } : i
|
|
175
|
+
),
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function remove(key: T[K]) {
|
|
180
|
+
_state.update(curr => ({
|
|
181
|
+
...curr,
|
|
182
|
+
value: curr.value.filter(i => i[field] !== key),
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
state: _state,
|
|
188
|
+
|
|
189
|
+
// selectors
|
|
190
|
+
all,
|
|
191
|
+
count,
|
|
192
|
+
meta,
|
|
193
|
+
loading,
|
|
194
|
+
error,
|
|
195
|
+
hasNextPage,
|
|
196
|
+
currentPage,
|
|
197
|
+
getByKey,
|
|
198
|
+
|
|
199
|
+
// actions
|
|
200
|
+
load,
|
|
201
|
+
loadPage,
|
|
202
|
+
loadNextPage,
|
|
203
|
+
refresh,
|
|
204
|
+
update,
|
|
205
|
+
remove,
|
|
206
|
+
reset,
|
|
207
|
+
clear,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -15,12 +15,7 @@ export const loginGuard: CanActivateFn = (route, state) => {
|
|
|
15
15
|
return storage.get(environment.tokenName).pipe(
|
|
16
16
|
map((token: any) => {
|
|
17
17
|
if (token) {
|
|
18
|
-
const appId = route.queryParams['appId'];
|
|
19
|
-
const redirect = route.queryParams['redirect'];
|
|
20
18
|
|
|
21
|
-
if (appId && redirect) {
|
|
22
|
-
window.location.href = redirect + '?token=' + token.token;
|
|
23
|
-
}
|
|
24
19
|
|
|
25
20
|
router.navigateByUrl('/home');
|
|
26
21
|
// helper.showPopup(
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
1
2
|
import { Component, forwardRef, input, Input, OnInit } from '@angular/core';
|
|
2
3
|
import { ControlValueAccessor, FormControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
|
|
3
4
|
import { AlertErrorInputComponent } from '@shared/widgets/alert-error-input/alert-error-input.component';
|
|
5
|
+
import { FloatLabelModule } from "primeng/floatlabel";
|
|
4
6
|
|
|
5
7
|
@Component({
|
|
6
8
|
selector: 'input-text',
|
|
7
9
|
styleUrls: ['./input-text.component.scss'],
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
imports: [
|
|
11
|
+
ReactiveFormsModule,
|
|
12
|
+
FormsModule,
|
|
13
|
+
AlertErrorInputComponent,
|
|
14
|
+
FloatLabelModule,
|
|
15
|
+
CommonModule
|
|
16
|
+
],
|
|
10
17
|
providers: [
|
|
11
18
|
{
|
|
12
19
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -15,17 +22,43 @@ import { AlertErrorInputComponent } from '@shared/widgets/alert-error-input/aler
|
|
|
15
22
|
},
|
|
16
23
|
],
|
|
17
24
|
template: `
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
<div class="rounded-lg bg-white px-4 py-2">
|
|
26
|
+
<label class="block text-[#657687] mb-0 text-sm" [for]="label()"
|
|
27
|
+
>{{label()}}</label>
|
|
28
|
+
@if(this.type == 'password'){
|
|
29
|
+
<div class="relative">
|
|
30
|
+
<input [autocomplete]="false" (ngModelChange)="onValueChange($event)" [(ngModel)]="value" [type]="showPassword ? 'text' : type"
|
|
31
|
+
class="w-full p-0! border-none! rounded-lg focus:outline-none! focus:ring-0"
|
|
32
|
+
[placeholder]="placeholder()" />
|
|
33
|
+
<i
|
|
34
|
+
class="pi absolute right-0 top-0 -translate-y-1/2 cursor-pointer text-gray-500 hover:text-gray-700"
|
|
35
|
+
[ngClass]="showPassword ? 'pi-eye' : 'pi-eye-slash'"
|
|
36
|
+
(click)="showPassword = !showPassword"
|
|
37
|
+
></i>
|
|
38
|
+
</div>
|
|
39
|
+
}
|
|
40
|
+
@else {
|
|
41
|
+
<input (ngModelChange)="onValueChange($event)" [(ngModel)]="value" [type]="type"
|
|
42
|
+
class="w-full p-0! border-none! rounded-lg focus:outline-none! focus:ring-0"
|
|
43
|
+
[placeholder]="placeholder()" />
|
|
44
|
+
}
|
|
45
|
+
</div>
|
|
21
46
|
<alert-error [control]="control" [label]="label()"></alert-error>
|
|
22
47
|
`
|
|
23
48
|
})
|
|
24
49
|
export class InputTextComponent implements ControlValueAccessor, OnInit {
|
|
25
50
|
|
|
51
|
+
showPassword = false;
|
|
26
52
|
@Input() control!: FormControl | any;
|
|
27
|
-
|
|
28
|
-
|
|
53
|
+
@Input() idLabel = "";
|
|
54
|
+
@Input() variant: "floating" | "normal" = "normal";
|
|
55
|
+
@Input() name = "";
|
|
56
|
+
@Input() typeLabel: "over" | "on" | "in" = "in";
|
|
57
|
+
@Input() type: "password" | "email" | "text" | "number" = "text";
|
|
58
|
+
|
|
59
|
+
placeholder = input("Enter your email"); //default
|
|
60
|
+
label = input("label"); //default
|
|
61
|
+
form: any = input();
|
|
29
62
|
|
|
30
63
|
value: any;
|
|
31
64
|
|
|
@@ -56,6 +89,9 @@ export class InputTextComponent implements ControlValueAccessor, OnInit {
|
|
|
56
89
|
onValueChange(value: any) {
|
|
57
90
|
this.onChange(value);
|
|
58
91
|
}
|
|
92
|
+
togglePassword() {
|
|
93
|
+
this.showPassword = !this.showPassword;
|
|
94
|
+
}
|
|
59
95
|
|
|
60
96
|
// Fungsi yang akan dipanggil ketika nilai diubah
|
|
61
97
|
onChange: (date: any) => void = () => { };
|
package/package.json
CHANGED