diff --git a/package.json b/package.json
index 7d7bfa3..ceb3143 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"babel-polyfill": "^6.26.0",
"connect-history-api-fallback": "^1.6.0",
"html-webpack-plugin": "^5.3.1",
+ "validator": "^13.6.0",
"webpack": "^5.38.1",
"webpack-dev-server": "^3.11.2"
},
diff --git a/src/components/app-main/AppMainElement.ts b/src/components/app-main/AppMainElement.ts
index 488dd49..bb631b5 100644
--- a/src/components/app-main/AppMainElement.ts
+++ b/src/components/app-main/AppMainElement.ts
@@ -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 };
diff --git a/src/components/index.ts b/src/components/index.ts
index 6e9857d..c4c598e 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1,2 +1,3 @@
export * from "./app-main/AppMainElement";
export * from "./app-link/AppLinkElement";
+export * from "./input-field/InputFieldElement";
diff --git a/src/components/input-field/InputFieldElement.ts b/src/components/input-field/InputFieldElement.ts
new file mode 100644
index 0000000..8486187
--- /dev/null
+++ b/src/components/input-field/InputFieldElement.ts
@@ -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`
+ ${this.label &&
+ html``}
+
+ ${this.error && html`${this.error}`}
+ `,
+ this
+ );
+ }
+}
+
+export type { InputFieldElement };
diff --git a/src/core/constants/index.ts b/src/core/constants/index.ts
new file mode 100644
index 0000000..a5c884f
--- /dev/null
+++ b/src/core/constants/index.ts
@@ -0,0 +1 @@
+export * from "./validatorErrors";
diff --git a/src/core/constants/validatorErrors.ts b/src/core/constants/validatorErrors.ts
new file mode 100644
index 0000000..f9e1659
--- /dev/null
+++ b/src/core/constants/validatorErrors.ts
@@ -0,0 +1,4 @@
+export const validatorErrors = {
+ required: "{- name} is required.",
+ isEmail: "{- name} needs to be email format.",
+};
diff --git a/src/core/services/app-service/AppService.ts b/src/core/services/app-service/AppService.ts
new file mode 100644
index 0000000..818bc81
--- /dev/null
+++ b/src/core/services/app-service/AppService.ts
@@ -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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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;
diff --git a/src/core/services/base-service/BaseService.ts b/src/core/services/base-service/BaseService.ts
index 9fb727b..94f2890 100644
--- a/src/core/services/base-service/BaseService.ts
+++ b/src/core/services/base-service/BaseService.ts
@@ -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);
};
}
diff --git a/src/core/services/http-service/HttpClient.ts b/src/core/services/http-service/HttpClient.ts
index 304b8c7..4bfe777 100644
--- a/src/core/services/http-service/HttpClient.ts
+++ b/src/core/services/http-service/HttpClient.ts
@@ -77,7 +77,13 @@ export default HttpClient;
async function createRequest(request: Request): Promise {
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) {
diff --git a/src/core/services/index.ts b/src/core/services/index.ts
index 47095cf..9b07a3d 100644
--- a/src/core/services/index.ts
+++ b/src/core/services/index.ts
@@ -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";
diff --git a/src/core/services/router-service/RouterService.ts b/src/core/services/router-service/RouterService.ts
index e134f34..439ecc4 100644
--- a/src/core/services/router-service/RouterService.ts
+++ b/src/core/services/router-service/RouterService.ts
@@ -17,12 +17,13 @@ class RouterService {
setRoutes = (routes: Array) => {
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
) {}
}
diff --git a/src/core/store/AuthStore.ts b/src/core/store/AuthStore.ts
new file mode 100644
index 0000000..1c2d258
--- /dev/null
+++ b/src/core/store/AuthStore.ts
@@ -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;
diff --git a/src/core/store/index.ts b/src/core/store/index.ts
new file mode 100644
index 0000000..e97a1fe
--- /dev/null
+++ b/src/core/store/index.ts
@@ -0,0 +1 @@
+export { default as AuthStore } from "./AuthStore";
diff --git a/src/core/utils/first-upper.ts b/src/core/utils/first-upper.ts
new file mode 100644
index 0000000..9531212
--- /dev/null
+++ b/src/core/utils/first-upper.ts
@@ -0,0 +1,4 @@
+export default function firstUpper(s: string): string {
+ if (typeof s !== "string") return "";
+ return s.charAt(0).toUpperCase() + s.slice(1);
+}
diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts
index b10241f..6ff2da0 100644
--- a/src/core/utils/index.ts
+++ b/src/core/utils/index.ts
@@ -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";
diff --git a/src/core/utils/random-id.ts b/src/core/utils/random-id.ts
new file mode 100644
index 0000000..713f9e5
--- /dev/null
+++ b/src/core/utils/random-id.ts
@@ -0,0 +1,3 @@
+export default function randomId() {
+ return "_" + Math.random().toString(36).substr(2, 5);
+}
diff --git a/src/pages/home-page/HomePageElement.ts b/src/pages/home-page/HomePageElement.ts
index 03c0585..9b35b8c 100644
--- a/src/pages/home-page/HomePageElement.ts
+++ b/src/pages/home-page/HomePageElement.ts
@@ -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);
}
diff --git a/src/pages/index.ts b/src/pages/index.ts
index bcef41b..7075d09 100644
--- a/src/pages/index.ts
+++ b/src/pages/index.ts
@@ -1 +1,2 @@
export * from "./home-page/HomePageElement";
+export * from "./register-page/RegisterPageElement";
diff --git a/src/pages/register-page/RegisterPageElement.ts b/src/pages/register-page/RegisterPageElement.ts
new file mode 100644
index 0000000..fb12534
--- /dev/null
+++ b/src/pages/register-page/RegisterPageElement.ts
@@ -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;
+ @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`
+
+ `,
+ this
+ );
+ }
+}
diff --git a/src/services/AuthService.ts b/src/services/AuthService.ts
new file mode 100644
index 0000000..1828f04
--- /dev/null
+++ b/src/services/AuthService.ts
@@ -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;
diff --git a/src/services/PingService.ts b/src/services/PingService.ts
index cad4809..0a7f6ce 100644
--- a/src/services/PingService.ts
+++ b/src/services/PingService.ts
@@ -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);
}
}
diff --git a/src/services/index.ts b/src/services/index.ts
index c9327e0..1c63abb 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -1 +1,2 @@
export { default as PingService } from "./PingService";
+export { default as AuthService } from "./AuthService";
diff --git a/yarn.lock b/yarn.lock
index f357d50..f9a7952 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4358,6 +4358,11 @@ v8-compile-cache@^2.2.0:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
+validator@^13.6.0:
+ version "13.6.0"
+ resolved "https://registry.yarnpkg.com/validator/-/validator-13.6.0.tgz#1e71899c14cdc7b2068463cb24c1cc16f6ec7059"
+ integrity sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==
+
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"