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
44 changes: 36 additions & 8 deletions src/autocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { fromEvent, Observable } from "rxjs";
import { ajax } from "rxjs/ajax";
import { debounceTime, distinctUntilChanged, map, switchMap, tap } from "rxjs/operators";
import { debounceTime, delay, distinctUntilChanged, filter, map, switchMap, tap, finalize, pluck } from "rxjs/operators";
import "./assets/styles/autocomplete.scss";
import "./assets/styles/styles.scss";
import { IUser } from "./models/user.model";
import { IRandomUserData, IUser, IUsersResponse } from "./models/user.model";

const autocompleteListEl = document.querySelector(".autocomplete__list");
const templateEl = document.getElementById('user-card') as HTMLTemplateElement
const usersRowEl = document.querySelector('.users-row')
const autocompleteUrl = "https://jsonplaceholder.typicode.com/users?username=";
const usersApiUrl = "https://randomuser.me/api/?results=10";

Expand Down Expand Up @@ -42,29 +44,40 @@ const addUserColumn = (name: string) => {
const fetchUserByName = (userName: string): Observable<IUser[]> =>
ajax.getJSON(`${autocompleteUrl}${userName}`);

const fetchAllUsers = (): Observable<IUsersResponse> =>
ajax.getJSON(usersApiUrl);

document.addEventListener("DOMContentLoaded", init);

const showLoader = (loader: Element) => loader.classList.remove('is-hidden')
const hideLoader = (loader: Element) => loader.classList.add('is-hidden')

function init() {
const autocomplete: HTMLInputElement = document.querySelector(
"#autocomplete__input"
);
const source$ = fromEvent(autocomplete, "input");
const loader = document.querySelector('.fullscreen-loader')

// 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
map(value => value.trim()),
filter(value => value.length > 3),
tap(() => showLoader(loader)),
debounceTime(100),
distinctUntilChanged(),
tap(userName => console.log("--next value", userName)),
switchMap(userName => fetchUserByName(userName))
// TODO: add delay 300ms
// TODO: add finalize operator to hide the loader
switchMap(userName => fetchUserByName(userName)),
delay(300),
tap(() => hideLoader(loader))
)
.subscribe(renderAutocomplete);

fetchAllUsers()
.pipe(pluck('results'))
.subscribe(renderUsersCards)
}

function renderAutocomplete(users: IUser[]) {
Expand All @@ -74,3 +87,18 @@ function renderAutocomplete(users: IUser[]) {

autocompleteListEl.innerHTML = usersListHtml.join();
}

const renderUsersCards = (users: IRandomUserData[]) => {
const titleEl = templateEl.content.querySelector('.card-title')
const imageEl = templateEl.content.querySelector('.card-img-top') as HTMLImageElement
const buttonEl = templateEl.content.querySelector('.btn') as HTMLAnchorElement

users.forEach((user, index) => {
titleEl.textContent = user.name.first
imageEl.src = user.picture.large
buttonEl.href = `${window.location.origin}/profile.html?userId=${index + 1}`

const clone = document.importNode(templateEl.content, true)
usersRowEl.appendChild(clone)
})
}
85 changes: 84 additions & 1 deletion src/models/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,87 @@ export interface IUser {
phone: string;
website: string;
company: Company;
}
}

interface Name {
title: string;
first: string;
last: string;
}

interface Coordinates {
latitude: string;
longitude: string;
}

interface Timezone {
offset: string;
description: string;
}

interface Location {
street: string;
city: string;
state: string;
postcode: string;
coordinates: Coordinates;
timezone: Timezone;
}

interface Login {
uuid: string;
username: string;
password: string;
salt: string;
md5: string;
sha1: string;
sha256: string;
}

interface Dob {
date: Date;
age: number;
}

interface Registered {
date: Date;
age: number;
}

interface Id {
name: string;
value: string;
}

interface Picture {
large: string;
medium: string;
thumbnail: string;
}

export interface IRandomUserData {
gender: string;
name: Name;
location: Location;
email: string;
login: Login;
dob: Dob;
registered: Registered;
phone: string;
cell: string;
id: Id;
picture: Picture;
nat: string;
}

interface Info {
seed: string;
results: number;
page: number;
version: string;
}

export interface IUsersResponse {
results: IRandomUserData[];
info: Info;
}
5 changes: 4 additions & 1 deletion src/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
</button>

<div
class="collapse navbar-collapse is-hidden"
class="collapse navbar-collapse"
id="navbarSupportedContent"
>
<ul class="navbar-nav mr-auto">
Expand All @@ -46,6 +46,9 @@
</nav>
<!-- header layout end -->
<div class="container py-4 my-2">
<div class="alert alert-danger is-hidden" role="alert">
User not found!
</div>
<div class="row">
<div class="col-md-4 pr-md-5">
<img
Expand Down
50 changes: 50 additions & 0 deletions src/profile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ajax } from "rxjs/ajax";
import { finalize } from 'rxjs/operators'
import "./assets/styles/styles.scss";
import authService from './services/auth.service';
import { IUser } from './models/user.model'

// TODO: fix navigation for logged user
// TODO: get userId from query param, fetch users data from api by id, hide loader
Expand Down Expand Up @@ -42,6 +46,32 @@ const userApiUrl = "https://jsonplaceholder.typicode.com/users/";
// }
// }

document.addEventListener("DOMContentLoaded", init);

function init() {
checkAuth()
getUser()
}

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

const hideLoader = () => {
const loader = document.querySelector('.fullscreen-loader')
loader.classList.add('is-hidden')
}

const getUser = () => {
const userId = getQueryParam('userId')
if (userId == null) window.location.href = window.location.origin
ajax.getJSON(`${userApiUrl}${userId}`)
.pipe(finalize(hideLoader))
.subscribe(render, onError)
}

/**
* Helper function to get query param on the page
*
Expand All @@ -53,3 +83,23 @@ function getQueryParam(param: string) {
return urlParams.get(param);
}

const render = (user: IUser) => {
const usernameEl = document.querySelector('.username')
const addressEl = document.querySelector('.user-address')
const companyNameEl = document.querySelector('.company-name')
const zipCodeEl = document.querySelector('.zip-code')
const websiteEl = document.querySelector('.user-website')
const companyEl = document.querySelector('.user-company')

usernameEl.textContent = user.name
addressEl.textContent = `${user.address.street } , ${user.address.city }`
companyNameEl.textContent = user.company.name
zipCodeEl.textContent = user.address.zipcode
websiteEl.textContent = user.website
companyEl.textContent = `${user.company.name}, ${user.company.catchPhrase}`
}

const onError = () => {
const alertEl = document.querySelector('.alert-danger')
alertEl.classList.remove('is-hidden')
}
2 changes: 1 addition & 1 deletion src/registration.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
</div>
<!-- form errors -->

<form class="form-horizontal mt-2">
<form class="form-horizontal mt-2" id="registration-form">
<fieldset>
<div id="legend">
<legend class="">Register</legend>
Expand Down
69 changes: 47 additions & 22 deletions src/registration.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import { fromEvent, combineLatest } from "rxjs";
import { map, startWith } from 'rxjs/operators'
import { number, object, string, ValidationError } from "yup";
import "./assets/styles/styles.scss";

document.addEventListener("DOMContentLoaded", init);

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

const createObservables = source =>
Object.entries(source).map(([key, value]) => {
const input = document.querySelector(`#${key}`)
return fromEvent(input, 'input')
.pipe(
mapToTarget,
startWith(value),
map(value => ({ [key]: value }))
)
})

const mapToData = (values) => values.reduce((acc, val) => ({ ...acc, ...val }), {})

function init() {
const registrationForm = {
username: "",
Expand All @@ -17,6 +34,9 @@ function init() {
// TIPS: you can use Object.keys, fromEvent, map, startWith, combineLatest, js reduce operator etc.

const formErrorsEl = document.querySelector(".form-errors");
const isValidFormEl = document.querySelector('.is-valid-form')
const formDataEl = document.querySelector('.form-data')
const formEl = document.querySelector('#registration-form')
const registrationValidatoinShema = object().shape({
username: string()
.required()
Expand All @@ -30,26 +50,31 @@ function init() {
age: number().required("Number is required")
});

// Example of validation, you have to create the same shape on change of validation fields
registrationValidatoinShema
.validate(
{
username: "wkdow",
email: "email@gmail.com",
password: "",
age: 2
},
{ abortEarly: false }
)
.then(validData => {
console.log("--handle valid data", validData);
formErrorsEl.innerHTML = "";
})
.catch((validationError: ValidationError) => {
console.log("--errors", validationError);
const erorrsList = validationError.errors.map(
error => `<div class="alert alert-danger" role="alert">${error}</div>`
);
formErrorsEl.innerHTML = erorrsList.join("");
});
const onFormChange = (data) => {
formDataEl.innerHTML = JSON.stringify(data)
registrationValidatoinShema
.validate(data, { abortEarly: false })
.then(validData => {
console.log("--handle valid data", validData);
formErrorsEl.innerHTML = "";
isValidFormEl.innerHTML = "true"
})
.catch((validationError: ValidationError) => {
console.log("--errors", validationError);
const erorrsList = validationError.errors.map(
error => `<div class="alert alert-danger" role="alert">${error}</div>`
);
formErrorsEl.innerHTML = erorrsList.join("");
isValidFormEl.innerHTML = "false"
});
}

const observables = createObservables(registrationForm)

combineLatest(observables)
.pipe(map(mapToData))
.subscribe(onFormChange)

fromEvent(formEl, 'submit')
.subscribe(e => e.preventDefault())
}