Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,601 changes: 2,600 additions & 3,001 deletions package-lock.json

Large diffs are not rendered by default.

200 changes: 88 additions & 112 deletions src/autocomplete.html
Original file line number Diff line number Diff line change
@@ -1,127 +1,103 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Home</title>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous"
/>
</head>

<body>
<!-- header layout start -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">Home</a>
<button
class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Home</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
</head>

<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/balance.html"
>Balance <span class="sr-only">(current)</span></a
>
</li>
<li class="nav-item">
<a class="nav-link active" href="/autocomplete.html"
>Autocomplete</a
>
</li>
</ul>
</div>
</nav>
<!-- header layout end -->
<div class="container">
<div class="row">
<div class="col-md-5">
<h2>Find user</h2>
<form class="form-inline">
<div class="form-group">
<label for="inputPassword6">Usename:</label>
<div class="autocomplete">
<input
type="text"
id="autocomplete__input"
class="form-control"
name="new Date()"
aria-describedby="passwordHelpInline"
/>
<ul class="list-group autocomplete__list"></ul>
</div>
<small id="passwordHelpInline" class="text-muted">
Minimum 3 symbols
</small>
<body>
<!-- header layout start -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">Home</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>

<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/balance.html">Balance <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/autocomplete.html">Autocomplete</a>
</li>
</ul>
</div>
</nav>
<!-- header layout end -->
<div class="container">
<div class="row">
<div class="col-md-5">
<h2>Find user</h2>
<form class="form-inline">
<div class="form-group">
<label for="inputPassword6">Usename:</label>
<div class="autocomplete">
<input type="text" id="autocomplete__input" class="form-control" name="new Date()"
aria-describedby="passwordHelpInline" />
<ul class="list-group autocomplete__list"></ul>
</div>
</form>
</div>
<div class="col-md-5">
<div class="pl-5">
<h4>
Available users
</h4>
<p>Bret</p>
<p>Antonette</p>
<p>Samantha</p>
<p>Karianne</p>
<p>Kamren</p>
<p>Maxime_Nienow</p>
<p>Delphine</p>
<p>Moriah.Stanton</p>
<small id="passwordHelpInline" class="text-muted">
Minimum 3 symbols
</small>
</div>
</form>
</div>
<div class="col-md-5">
<div class="pl-5">
<h4>
Available users
</h4>
<p>Bret</p>
<p>Antonette</p>
<p>Samantha</p>
<p>Karianne</p>
<p>Kamren</p>
<p>Maxime_Nienow</p>
<p>Delphine</p>
<p>Moriah.Stanton</p>
</div>
</div>
<div class="row users-row"></div>
</div>
<div class="row users-row"></div>
</div>

<!-- loader -->
<div class="fullscreen-loader text-primary is-hidden">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
<!-- loader -->
<div class="fullscreen-loader text-primary is-hidden">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
<!-- loader end -->
</div>
<!-- loader end -->

<template id="user-card">
<div class="col-md-3">
<div class="card">
<img src="" class="card-img-top user-card__image" alt="" />
<div class="card-body">
<h5 class="card-title user-card__title"></h5>
<a href="" class="btn btn-primary">Open User</a>
</div>
<template id="user-card">
<div class="col-md-3">
<div class="card">
<img src="" class="card-img-top user-card__image" alt="" />
<div class="card-body">
<h5 class="card-title user-card__title"></h5>
<a href="" class="btn btn-primary">Open User</a>
</div>
</div>
</template>
</div>
</template>

<!-- bootstrap -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
<!-- bootstrap end -->
</body>

<!-- bootstrap -->
<script
src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"
></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"
></script>
<!-- bootstrap end -->
</body>
</html>
</html>
85 changes: 47 additions & 38 deletions src/autocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,51 @@
import { fromEvent, Observable } from "rxjs";
import { fromEvent, Observable, of } from "rxjs";
import { ajax } from "rxjs/ajax";
import { debounceTime, distinctUntilChanged, map, switchMap, tap } from "rxjs/operators";
import { debounceTime, delay, distinctUntilChanged, filter, map, pluck, switchMap, tap, mergeMap } from "rxjs/operators";
import "./assets/styles/autocomplete.scss";
import "./assets/styles/styles.scss";
import { IUser } from "./models/user.model";
import { UserResponse, Result as UserResult } from "./models/user.response";
import { showHideLoader } from "./helpers/spinner.helper";

const autocompleteListEl = document.querySelector(".autocomplete__list");
const userTemplateContent = (document.querySelector("#user-card") as HTMLTemplateElement).content;

const autocompleteUrl = "https://jsonplaceholder.typicode.com/users?username=";
const usersApiUrl = "https://randomuser.me/api/?results=10";

// TODO: 1. Create a function that makes request to usersApiUrl using ajax.getJSON()
// (the response will be in the following format https://randomuser.me/documentation#format) - `fetchUsers`
// TODO: 2. Create the model of the response of p. 2 as Observable<UsersResponse> using http://www.json2ts.com/
// TODO: 3. Show results using <template> tag with id="user-card", for ex. create method renderUsersCards,
// show user.name.first in Title, user.picture.large in img.src and link should open `${window.location.origin}/profile.html?userId=${index}`
// where index just user's sequence number

// How to use template tags? - https://developer.mozilla.org/ru/docs/Web/HTML/Element/template

// basically it will look like this:
// Select template
// const template = document.getElementById('user-card') as HTMLTemplateElement;
// const templateContent = template.content;
// Select nodes in template and add content
// Clone it and append to selector
// const clone = document.importNode(templateContent, true);
// document.querySelector(some-selector).appendChild(clone);

// p.s. in response you can use pipeable operator `pluck` to take `results`
// p.s. Typescript can't understand which Node is the image or anchor etc., so use such syntax to help TS
// template = document.getElementById('user-card') as HTMLTemplateElement
// image = ... as HTMLImageElement
// achor = ... as HTMLAnchorElement

// TODO: finish aftercomplete's logic

const addUserColumn = (name: string) => {
return `<li class="list-group-item">${name}</li>`;
};

const fetchUserByName = (userName: string): Observable<IUser[]> =>
ajax.getJSON(`${autocompleteUrl}${userName}`);

const fetchUsers = (): Observable<UserResponse[]> =>
ajax.getJSON(`${usersApiUrl}`);

document.addEventListener("DOMContentLoaded", init);

function init() {
initAutocomplete();
initUserRender();
}

function initAutocomplete() {
const autocomplete: HTMLInputElement = document.querySelector(
"#autocomplete__input"
);
const source$ = fromEvent(autocomplete, "input");

// LOADER exists in Markup, just toggle class 'is-hidden'
source$
.pipe(
map(e => (e.target as HTMLInputElement).value),
// TODO: add triming
// TODO: filter by min length of the 4
// TODO: show Loader
debounceTime(100),
map(e => (e.target as HTMLInputElement).value.trim()),
filter(x => x.length >= 3),
debounceTime(1000),
distinctUntilChanged(),
tap(userName => console.log("--next value", userName)),
switchMap(userName => fetchUserByName(userName))
// TODO: add delay 300ms
// TODO: add finalize operator to hide the loader
tap(_ => showHideLoader(true)),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tap(() => method()) is more common

switchMap(userName => fetchUserByName(userName)),
delay(300),
tap(_ => showHideLoader(false)),
)
.subscribe(renderAutocomplete);
}
Expand All @@ -74,3 +57,29 @@ function renderAutocomplete(users: IUser[]) {

autocompleteListEl.innerHTML = usersListHtml.join();
}

function initUserRender() {
of(1)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need of(..) ?

I guess would be better to do like so:

ajax.getJSON(usersApiUrl).pipe(
pluck('results')
).subsribe(...

.pipe(
switchMap(_ => fetchUsers()),
pluck<UserResponse[], UserResult[]>('results'),
mergeMap(data => data),
map(createUserTemplate),
)
.subscribe(userTemplate => document.querySelector(".users-row").appendChild(userTemplate));
}

function createUserTemplate(item: UserResult, index: number): DocumentFragment {
const templateClone = document.importNode(userTemplateContent, true);

const image = templateClone.querySelector(".user-card__image") as HTMLImageElement;
image.src = item.picture.large;

const button = templateClone.querySelector(".btn") as HTMLAnchorElement;
button.href = `${window.location.origin}/profile.html?userId=${index + 1}`;

const title = templateClone.querySelector(".user-card__title") as HTMLHeadingElement;
title.textContent = `${item.name.title} ${item.name.first} ${item.name.last}`

return templateClone;
}
29 changes: 7 additions & 22 deletions src/balance.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import "./assets/styles/styles.scss";

import { fromEvent, combineLatest } from "rxjs";
import { map, startWith } from "rxjs/operators";
import authService from './services/auth.service';
import { combineLatest } from "rxjs";
import { map } from "rxjs/operators";
import { handleUserAuthorization } from "./helpers/auth.helper";
import { createPipelineFromValue$ } from "./helpers/pipeline.helper";

document.addEventListener("DOMContentLoaded", init);

function init() {
initUser();
handleUserAuthorization();
initBalance();
}


function initUser() {
if (!authService.isAuthorized) {
window.location.href = window.location.origin;
}
}


function initBalance() {
const uahInput: HTMLInputElement = document.querySelector("#uah");
const usdInput: HTMLInputElement = document.querySelector("#usd");
Expand All @@ -27,16 +20,8 @@ function initBalance() {
uahInput.value = "30";
usdInput.value = "50";

const mapToTarget = map((e: Event) => (e.target as HTMLInputElement).value);

const uahInputSource$ = fromEvent(uahInput, "input").pipe(
mapToTarget,
startWith(uahInput.value)
);
const usdInputSource$ = fromEvent(usdInput, "input").pipe(
mapToTarget,
startWith(usdInput.value)
);
const uahInputSource$ = createPipelineFromValue$(uahInput);
const usdInputSource$ = createPipelineFromValue$(usdInput);

combineLatest(uahInputSource$, usdInputSource$)
.pipe(map(([uah, usd]) => [Number(uah), Number(usd)]))
Expand Down
8 changes: 8 additions & 0 deletions src/helpers/auth.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import authService from './../services/auth.service';
import { redirectToMain } from "./redirection.helper";

export function handleUserAuthorization(): void {
if (!authService.isAuthorized) {
redirectToMain();
}
}
Loading