mirror of
https://github.com/FJurmanovic/wallet-web.git
synced 2026-02-06 06:08:10 +00:00
added authorization stores and fixed http client
This commit is contained in:
@@ -2,16 +2,24 @@ import { attr, targets, controller, target } from "@github/catalyst";
|
||||
import { closest, index, update, isTrue } from "core/utils";
|
||||
import { html, render, until } from "@github/jtml";
|
||||
import { PingService } from "services/";
|
||||
import { RouterService } from "core/services";
|
||||
import { AppService, HttpClient, RouterService } from "core/services";
|
||||
import { AuthStore } from "core/store";
|
||||
|
||||
@controller
|
||||
class AppMainElement extends HTMLElement {
|
||||
public routerService: RouterService;
|
||||
public authStore: AuthStore;
|
||||
private httpClient: HttpClient;
|
||||
public appService: AppService;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
connectedCallback() {
|
||||
this.httpClient = new HttpClient();
|
||||
this.appService = new AppService(this, this.httpClient);
|
||||
this.routerService = new RouterService(this);
|
||||
this.authStore = new AuthStore(this.appService);
|
||||
this.routerService.setRoutes([
|
||||
{
|
||||
path: "/",
|
||||
@@ -21,10 +29,30 @@ class AppMainElement extends HTMLElement {
|
||||
{
|
||||
path: "/home",
|
||||
component: "home-page",
|
||||
middleware: this.isAuth,
|
||||
},
|
||||
{
|
||||
path: "/rb",
|
||||
component: "register-page",
|
||||
},
|
||||
{
|
||||
path: "/unauthorized",
|
||||
component: "register-page",
|
||||
},
|
||||
{
|
||||
path: "token-expired",
|
||||
component: "register-page",
|
||||
},
|
||||
]);
|
||||
this.routerService.init();
|
||||
}
|
||||
|
||||
isAuth = () => {
|
||||
if (this.authStore?.token == null) {
|
||||
this.routerService.goTo("/unauthorized");
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export type { AppMainElement };
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./app-main/AppMainElement";
|
||||
export * from "./app-link/AppLinkElement";
|
||||
export * from "./input-field/InputFieldElement";
|
||||
|
||||
81
src/components/input-field/InputFieldElement.ts
Normal file
81
src/components/input-field/InputFieldElement.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { attr, targets, controller, target } from "@github/catalyst";
|
||||
import { closest, index, update, isTrue, firstUpper } from "core/utils";
|
||||
import { html, render, until } from "@github/jtml";
|
||||
import { PingService } from "services/";
|
||||
import { AppMainElement } from "components/app-main/AppMainElement";
|
||||
import { RouterService } from "core/services";
|
||||
import randomId from "core/utils/random-id";
|
||||
import validator from "validator";
|
||||
import { validatorErrors } from "core/constants";
|
||||
|
||||
@controller
|
||||
class InputFieldElement extends HTMLElement {
|
||||
@closest appMain: AppMainElement;
|
||||
@attr name: string;
|
||||
@attr type: string;
|
||||
@attr label: string;
|
||||
@attr rules: string;
|
||||
@target main: HTMLElement;
|
||||
@target inp: HTMLElement;
|
||||
error: string;
|
||||
randId: string;
|
||||
routerService: RouterService;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
@update
|
||||
public connectedCallback(): void {
|
||||
this.randId = `${name}${randomId()}`;
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return !!this.error;
|
||||
}
|
||||
|
||||
@update
|
||||
validate(): boolean {
|
||||
let _return = true;
|
||||
const rules = this.rules?.split("|").filter((a) => a);
|
||||
const value = (this.inp as HTMLInputElement)?.value;
|
||||
rules
|
||||
.slice()
|
||||
.reverse()
|
||||
.forEach((rule) => {
|
||||
let valid = true;
|
||||
if (rule == "required") {
|
||||
if (value === "") valid = false;
|
||||
} else {
|
||||
if (validator.hasOwnProperty(rule)) {
|
||||
valid = validator?.[rule]?.(value);
|
||||
}
|
||||
}
|
||||
if (!valid) {
|
||||
const error = validatorErrors[rule]?.replaceAll(
|
||||
"{- name}",
|
||||
firstUpper(this.name?.toString())
|
||||
);
|
||||
_return = false;
|
||||
this.error = error;
|
||||
}
|
||||
});
|
||||
if (_return) {
|
||||
this.error = null;
|
||||
}
|
||||
return _return;
|
||||
}
|
||||
|
||||
update() {
|
||||
render(
|
||||
html`<span data-target="input-field.main">
|
||||
${this.label &&
|
||||
html`<label for="${this.randId}">${this.label}</label>`}
|
||||
<input type="${this.type}" data-target="input-field.inp" />
|
||||
${this.error && html`<span>${this.error}</span>`}
|
||||
</span>`,
|
||||
this
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type { InputFieldElement };
|
||||
1
src/core/constants/index.ts
Normal file
1
src/core/constants/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./validatorErrors";
|
||||
4
src/core/constants/validatorErrors.ts
Normal file
4
src/core/constants/validatorErrors.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const validatorErrors = {
|
||||
required: "{- name} is required.",
|
||||
isEmail: "{- name} needs to be email format.",
|
||||
};
|
||||
135
src/core/services/app-service/AppService.ts
Normal file
135
src/core/services/app-service/AppService.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { AppMainElement } from "components/";
|
||||
import { HttpClient } from "..";
|
||||
|
||||
class AppService {
|
||||
constructor(
|
||||
public appMain: AppMainElement,
|
||||
public httpClient: HttpClient
|
||||
) {}
|
||||
|
||||
post = async (
|
||||
url: string,
|
||||
data: Object,
|
||||
headersParam: HeadersInit
|
||||
): Promise<any> => {
|
||||
headersParam = {
|
||||
...headersParam,
|
||||
Authorization: `BEARER ${this.appMain?.authStore?.token}`,
|
||||
};
|
||||
try {
|
||||
const response = await this.httpClient.post(
|
||||
url,
|
||||
data,
|
||||
headersParam
|
||||
);
|
||||
if (
|
||||
response?.statusCode == 400 ||
|
||||
response?.statusCode == 500 ||
|
||||
response?.statusCode == 401
|
||||
) {
|
||||
if (response?.statusCode == 401) {
|
||||
this.appMain.authStore.token = null;
|
||||
this.appMain.routerService.goTo("/token-expired");
|
||||
}
|
||||
throw response;
|
||||
}
|
||||
return response;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
put = async (
|
||||
url: string,
|
||||
data: Object,
|
||||
headersParam: HeadersInit
|
||||
): Promise<any> => {
|
||||
headersParam = {
|
||||
...headersParam,
|
||||
Authorization: `BEARER ${this.appMain?.authStore?.token}`,
|
||||
};
|
||||
try {
|
||||
const response = await this.httpClient.put(url, data, headersParam);
|
||||
if (
|
||||
response?.statusCode == 400 ||
|
||||
response?.statusCode == 500 ||
|
||||
response?.statusCode == 401
|
||||
) {
|
||||
if (response?.statusCode == 401) {
|
||||
this.appMain.authStore.token = null;
|
||||
this.appMain.routerService.goTo("/token-expired");
|
||||
}
|
||||
throw response;
|
||||
}
|
||||
return response;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
delete = async (
|
||||
url: string,
|
||||
data: Object,
|
||||
headersParam: HeadersInit
|
||||
): Promise<any> => {
|
||||
headersParam = {
|
||||
...headersParam,
|
||||
Authorization: `BEARER ${this.appMain?.authStore?.token}`,
|
||||
};
|
||||
try {
|
||||
const response = await this.httpClient.delete(
|
||||
url,
|
||||
data,
|
||||
headersParam
|
||||
);
|
||||
if (
|
||||
response?.statusCode == 400 ||
|
||||
response?.statusCode == 500 ||
|
||||
response?.statusCode == 401
|
||||
) {
|
||||
if (response?.statusCode == 401) {
|
||||
this.appMain.authStore.token = null;
|
||||
this.appMain.routerService.goTo("/token-expired");
|
||||
}
|
||||
throw response;
|
||||
}
|
||||
return response;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
get = async (
|
||||
url: string,
|
||||
params: Object,
|
||||
headersParam: HeadersInit
|
||||
): Promise<any> => {
|
||||
headersParam = {
|
||||
...headersParam,
|
||||
Authorization: `BEARER ${this.appMain?.authStore?.token}`,
|
||||
};
|
||||
try {
|
||||
const response = await this.httpClient.get(
|
||||
url,
|
||||
params,
|
||||
headersParam
|
||||
);
|
||||
if (
|
||||
response?.statusCode == 400 ||
|
||||
response?.statusCode == 500 ||
|
||||
response?.statusCode == 401
|
||||
) {
|
||||
if (response?.statusCode == 401) {
|
||||
this.appMain.authStore.token = null;
|
||||
this.appMain.routerService.goTo("/token-expired");
|
||||
}
|
||||
throw response;
|
||||
}
|
||||
return response;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default AppService;
|
||||
@@ -1,29 +1,26 @@
|
||||
import { HttpClient } from "core/services";
|
||||
import { AppService, HttpClient } from "core/services";
|
||||
|
||||
class BaseService {
|
||||
private httpClient: HttpClient;
|
||||
constructor(private endpoint: string) {
|
||||
this.httpClient = new HttpClient();
|
||||
}
|
||||
constructor(public endpoint: string, public appService: AppService) {}
|
||||
|
||||
getAll = (headers?: HeadersInit) => {
|
||||
return this.httpClient.get(this.endpoint, null, headers);
|
||||
return this.appService.get(this.endpoint, null, headers);
|
||||
};
|
||||
|
||||
get = (params?: Object, headers?: HeadersInit) => {
|
||||
return this.httpClient.get(this.endpoint, params, headers);
|
||||
return this.appService.get(this.endpoint, params, headers);
|
||||
};
|
||||
|
||||
put = (data?: Object, headers?: HeadersInit) => {
|
||||
return this.httpClient.put(this.endpoint, data, headers);
|
||||
return this.appService.put(this.endpoint, data, headers);
|
||||
};
|
||||
|
||||
post = (data?: Object, headers?: HeadersInit) => {
|
||||
return this.httpClient.post(this.endpoint, data, headers);
|
||||
return this.appService.post(this.endpoint, data, headers);
|
||||
};
|
||||
|
||||
delete = (data?: Object, headers?: HeadersInit) => {
|
||||
return this.httpClient.delete(this.endpoint, data, headers);
|
||||
return this.appService.delete(this.endpoint, data, headers);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,13 @@ export default HttpClient;
|
||||
|
||||
async function createRequest(request: Request): Promise<Response> {
|
||||
let response: Response = await fetch(request);
|
||||
if (!response.ok && response.status !== 403 && response.status !== 400) {
|
||||
if (
|
||||
!response.ok &&
|
||||
response.status !== 403 &&
|
||||
response.status !== 400 &&
|
||||
response.status !== 401 &&
|
||||
response.status !== 500
|
||||
) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
} else {
|
||||
if (response.headers.get("Content-Type") !== null) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as HttpClient } from "./http-service/HttpClient";
|
||||
export { default as BaseService } from "./base-service/BaseService";
|
||||
export { default as RouterService } from "./router-service/RouterService";
|
||||
export { default as AppService } from "./app-service/AppService";
|
||||
|
||||
@@ -17,12 +17,13 @@ class RouterService {
|
||||
setRoutes = (routes: Array<any>) => {
|
||||
if (!Array.isArray(this._routes)) this._routes = [];
|
||||
routes.forEach((route) => {
|
||||
const { path, component, data, layout } = route;
|
||||
const { path, component, data, layout, middleware } = route;
|
||||
const _routeState: RouteState = new RouteState(
|
||||
path,
|
||||
component,
|
||||
data,
|
||||
layout
|
||||
layout,
|
||||
middleware
|
||||
);
|
||||
this._routes?.push(_routeState);
|
||||
});
|
||||
@@ -30,10 +31,13 @@ class RouterService {
|
||||
|
||||
update() {
|
||||
if (!this._routes) return;
|
||||
const path = window.location.pathname;
|
||||
const _mainRoot = this.mainRoot;
|
||||
for (const route of this._routes) {
|
||||
const path = window.location.pathname;
|
||||
if (path == route.path) {
|
||||
const _mainRoot = this.mainRoot;
|
||||
if (route.middleware && typeof route.middleware == "function") {
|
||||
if (route.middleware()) return;
|
||||
}
|
||||
let changed: boolean = false;
|
||||
if (_mainRoot?.childNodes.length > 0) {
|
||||
console.log(_mainRoot.childNodes);
|
||||
@@ -94,6 +98,7 @@ class RouterService {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_mainRoot.innerHTML = "404 - Not found";
|
||||
}
|
||||
|
||||
@update
|
||||
@@ -101,13 +106,27 @@ class RouterService {
|
||||
if (!Array.isArray(this.historyStack)) this.historyStack = [];
|
||||
const _index = this._routes.findIndex((route) => route.path === path);
|
||||
if (_index >= 0) {
|
||||
this.historyStack.push(this._routes[_index]);
|
||||
const newRoute = this._routes[_index];
|
||||
this.historyStack.push(newRoute);
|
||||
const url = new URL(window.location.toString());
|
||||
url.pathname = path;
|
||||
window.history.pushState({}, "", url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@update
|
||||
goBack() {
|
||||
if (!Array.isArray(this.historyStack)) this.historyStack = [];
|
||||
const lenHistory = this.historyStack.length;
|
||||
if (lenHistory > 1) {
|
||||
const nextRoute = this.historyStack[lenHistory - 2];
|
||||
const url = new URL(window.location.toString());
|
||||
url.pathname = nextRoute.path;
|
||||
window.history.pushState({}, "", url.toString());
|
||||
this.historyStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@update
|
||||
init() {}
|
||||
}
|
||||
@@ -117,7 +136,8 @@ class RouteState {
|
||||
public path: string,
|
||||
public component: string,
|
||||
public data: any,
|
||||
public layout: string
|
||||
public layout: string,
|
||||
public middleware: any
|
||||
) {}
|
||||
}
|
||||
|
||||
|
||||
54
src/core/store/AuthStore.ts
Normal file
54
src/core/store/AuthStore.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { AppService } from "core/services";
|
||||
import { AuthService } from "services/";
|
||||
|
||||
class AuthStore {
|
||||
private _token;
|
||||
private _userDetails;
|
||||
private authService: AuthService;
|
||||
constructor(private appService: AppService) {
|
||||
this.token = localStorage.getItem("token");
|
||||
this.authService = new AuthService(this.appService);
|
||||
}
|
||||
|
||||
get token() {
|
||||
return this._token;
|
||||
}
|
||||
|
||||
set token(token) {
|
||||
this._token = token;
|
||||
localStorage.setItem("token", token);
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this._userDetails;
|
||||
}
|
||||
|
||||
set user(userDetails) {
|
||||
this._userDetails = userDetails;
|
||||
}
|
||||
|
||||
userLogin = async (formObject) => {
|
||||
try {
|
||||
const response = await this.authService.login(formObject);
|
||||
if (response?.token) {
|
||||
this.token = response.token;
|
||||
} else {
|
||||
this.token = null;
|
||||
localStorage.removeItem("token");
|
||||
}
|
||||
return response;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
userRegister = async (formObject) => {
|
||||
try {
|
||||
await this.authService.register(formObject);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default AuthStore;
|
||||
1
src/core/store/index.ts
Normal file
1
src/core/store/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as AuthStore } from "./AuthStore";
|
||||
4
src/core/utils/first-upper.ts
Normal file
4
src/core/utils/first-upper.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default function firstUpper(s: string): string {
|
||||
if (typeof s !== "string") return "";
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
@@ -3,3 +3,4 @@ export { default as update } from "./update-deco";
|
||||
export { default as index } from "./index-deco";
|
||||
export { default as closest } from "./closest-deco";
|
||||
export { default as isTrue } from "./isTrue";
|
||||
export { default as firstUpper } from "./first-upper";
|
||||
|
||||
3
src/core/utils/random-id.ts
Normal file
3
src/core/utils/random-id.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function randomId() {
|
||||
return "_" + Math.random().toString(36).substr(2, 5);
|
||||
}
|
||||
@@ -2,21 +2,25 @@ import { attr, targets, controller, target } from "@github/catalyst";
|
||||
import { closest, index, update, isTrue } from "core/utils";
|
||||
import { html, render, until } from "@github/jtml";
|
||||
import { PingService } from "services/";
|
||||
import { AppMainElement } from "components/";
|
||||
|
||||
@controller
|
||||
class HomePageElement extends HTMLElement {
|
||||
private pingService: PingService;
|
||||
@closest appMain: AppMainElement;
|
||||
constructor() {
|
||||
super();
|
||||
this.pingService = new PingService();
|
||||
}
|
||||
@update
|
||||
connectedCallback() {}
|
||||
connectedCallback() {
|
||||
this.pingService = new PingService(this.appMain?.appService);
|
||||
this.getPong();
|
||||
}
|
||||
|
||||
getPong = async () => {
|
||||
try {
|
||||
const response = await this.pingService.getAll();
|
||||
return response.api;
|
||||
console.log(response);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./home-page/HomePageElement";
|
||||
export * from "./register-page/RegisterPageElement";
|
||||
|
||||
85
src/pages/register-page/RegisterPageElement.ts
Normal file
85
src/pages/register-page/RegisterPageElement.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { attr, targets, controller, target } from "@github/catalyst";
|
||||
import { closest, index, update, isTrue } from "core/utils";
|
||||
import { html, render, until } from "@github/jtml";
|
||||
import { AuthService, PingService } from "services/";
|
||||
import { AppMainElement, InputFieldElement } from "components/";
|
||||
|
||||
@controller
|
||||
class RegisterPageElement extends HTMLElement {
|
||||
@targets inputs: Array<InputFieldElement>;
|
||||
@closest appMain: AppMainElement;
|
||||
authService: AuthService;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@update
|
||||
connectedCallback() {
|
||||
this.authService = new AuthService(this.appMain.appService);
|
||||
}
|
||||
|
||||
get values(): Object {
|
||||
const formObject = {};
|
||||
this.inputs.forEach((input: InputFieldElement) => {
|
||||
const inputType = input.inp;
|
||||
formObject[input.name] = (inputType as HTMLInputElement).value;
|
||||
});
|
||||
return formObject;
|
||||
}
|
||||
|
||||
onSubmit = async () => {
|
||||
try {
|
||||
if (!this.validate()) {
|
||||
return;
|
||||
}
|
||||
const response = await this.appMain.authStore.userLogin(
|
||||
this.values
|
||||
);
|
||||
console.log(response);
|
||||
|
||||
if (response?.token) {
|
||||
this.appMain.routerService.goTo("/");
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
validate(): boolean {
|
||||
let _return = true;
|
||||
this.inputs.forEach((input: InputFieldElement) => {
|
||||
const valid: boolean = input.validate();
|
||||
if (!valid) _return = false;
|
||||
});
|
||||
return _return;
|
||||
}
|
||||
|
||||
update() {
|
||||
render(
|
||||
html`
|
||||
<form>
|
||||
<input-field
|
||||
data-type="email"
|
||||
data-name="email"
|
||||
data-label="E-mail"
|
||||
data-targets="register-page.inputs"
|
||||
data-rules="required|isEmail"
|
||||
></input-field>
|
||||
<input-field
|
||||
data-type="password"
|
||||
data-name="password"
|
||||
data-label="Password"
|
||||
data-targets="register-page.inputs"
|
||||
>
|
||||
</input-field>
|
||||
<button
|
||||
type="button"
|
||||
data-action="click:register-page#onSubmit"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</form>
|
||||
`,
|
||||
this
|
||||
);
|
||||
}
|
||||
}
|
||||
15
src/services/AuthService.ts
Normal file
15
src/services/AuthService.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { AppService, BaseService } from "core/services";
|
||||
|
||||
class PingService extends BaseService {
|
||||
constructor(appService: AppService) {
|
||||
super("/auth", appService);
|
||||
}
|
||||
login = (data?: Object, headers?: HeadersInit) => {
|
||||
return this.appService.post(this.endpoint + "/login", data, headers);
|
||||
};
|
||||
register = (data?: Object, headers?: HeadersInit) => {
|
||||
return this.appService.post(this.endpoint + "/register", data, headers);
|
||||
};
|
||||
}
|
||||
|
||||
export default PingService;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BaseService } from "core/services";
|
||||
import { AppService, BaseService } from "core/services";
|
||||
|
||||
class PingService extends BaseService {
|
||||
constructor() {
|
||||
super("/api");
|
||||
constructor(appService: AppService) {
|
||||
super("/wallet", appService);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { default as PingService } from "./PingService";
|
||||
export { default as AuthService } from "./AuthService";
|
||||
|
||||
Reference in New Issue
Block a user