diff --git a/css/styles.css b/css/styles.css index b1f1e31..e2913fe 100644 --- a/css/styles.css +++ b/css/styles.css @@ -13,13 +13,21 @@ html { padding-bottom: 5vh; overflow-y: scroll; } -[contenteditable='true'] { - border-bottom: solid #00f0b5 2px; -} -.profile-info { + +.user-info { border-bottom: 2px white; } +.user-info[contenteditable='true'] { + /* border-bottom: solid #00f0b5 2px; */ + color: #05c643 !important; +} + +.user-info.edited[contenteditable='true'] { + /* border-bottom: solid #f28202 2px; */ + color: #f28202 !important; +} + #loader { position: fixed; top: 50%; @@ -76,3 +84,23 @@ html { transform: rotate(360deg); } } + +/* +PROFILE PAGE CUSTOM STYLES + */ + +#edit, +#save { + top: 10px; + right: 10px; +} + +#cancel { + top: 10px; + left: 10px; +} + +#remove { + bottom: 10px; + right: 10px; +} diff --git a/html/profile.html b/html/profile.html index 517bcc4..bd8bb19 100644 --- a/html/profile.html +++ b/html/profile.html @@ -40,38 +40,57 @@
-
+
+ edit + save + cancel + delete
- stack photo +
-

-
Personal information
-
    + + +
    • person -

      +
    • email -

      + +
    • +
    • + link +
    • work -

      + +
    • +
    • + today +
    • - location_city -

      Dublin, UK

      + translate + +
    • +
    • + public +
-
- +
+
+

Skills

+
+
@@ -82,8 +101,7 @@
Personal information
crossorigin="anonymous"> - - + \ No newline at end of file diff --git a/index.html b/index.html index 6b3318c..8269751 100644 --- a/index.html +++ b/index.html @@ -8,8 +8,6 @@ CV App - @@ -44,7 +42,7 @@ -
+
diff --git a/js/edit-profile.js b/js/edit-profile.js deleted file mode 100644 index 16e7277..0000000 --- a/js/edit-profile.js +++ /dev/null @@ -1,14 +0,0 @@ -function edit() { - var Button = document.getElementById("edit"); - if (Button.className == "w-25 border border-info mt-2 rounded") { - Button.innerHTML = "Save"; - Button.className += " editing"; - document.querySelectorAll('p').forEach(el => el.setAttribute('contenteditable', true)); - document.querySelector('h2').setAttribute('contenteditable', true); - } else { - document.querySelectorAll('p').forEach(el => { el.setAttribute('contenteditable', false); console.log(el.textContent); }); - document.querySelector('h2').setAttribute('contenteditable', false); - Button.className = "w-25 border border-info mt-2 rounded"; - Button.innerHTML = "Edit"; - } -}; \ No newline at end of file diff --git a/js/enterProfile.js b/js/enterProfile.js deleted file mode 100644 index 6e4ef71..0000000 --- a/js/enterProfile.js +++ /dev/null @@ -1,27 +0,0 @@ -$.ajax({ - url: "https://jsonplaceholder.typicode.com/users", - contentType: "application/json" - }).done(function (data){ - var userid = window.location.search; - - var filterUser = data.filter(function(user){ - return user.id == userid[userid.length - 1]; - }); - - var nameUser = document.querySelector("h2"); - nameUser.innerHTML = filterUser[0].name; - - var username = document.querySelector("#username"); - username.innerHTML = filterUser[0].username; - - var email = document.querySelector("#email"); - email.innerHTML = filterUser[0].email; - - var company = document.querySelector("#company"); - company.innerHTML = filterUser[0].company.name; - - // var city = document.querySelector("#city"); - // city.innerHTML = filterUser[0].city; - }); - - diff --git a/js/infinite_scroll.js b/js/infinite_scroll.js index e16b63e..6f99641 100644 --- a/js/infinite_scroll.js +++ b/js/infinite_scroll.js @@ -1,27 +1,42 @@ const cardsContainer = document.querySelector('#cards-container'); const searchInput = document.getElementById("nav-input"); +// State variables to control activation of infinite scroll, page of users to request and store of all users requested let isFetchAllowed = true; +let currentUsersPage = 1; let loadedUsers = []; // Initial content load from API function fetchUsersData() { if (searchInput.value == '') { $.ajax({ - url: "https://jsonplaceholder.typicode.com/users" + url: `https://cv-mobile-api.herokuapp.com/api/users/pages/${currentUsersPage}` }).done((data) => { - // Show the loader while loading the content - // showLoader(); - // Iterate over the data array and extract the info for each user in a card variable - data.map( (user) => { - const card = renderCard( user.name, user.username, user.email, user.company.name, user.id); - // Add the card with the user info to the DOM - cardsContainer.innerHTML += card; - // Save the user inside a collections with all loaded users - loadedUsers.push(user); - }); - // Hide the loader after the content has loaded - hideLoader(); + + if (data.length === 10) { + data.map( (user) => { + user.highlight = []; + const card = renderCard(user); + cardsContainer.innerHTML += card; + loadedUsers.push(user); + }); + // Add one to the current page of users for the next request if the page has 10 users + currentUsersPage++; + + } else if (data.length < 10) { + + if (data[0]._id !== loadedUsers[loadedUsers.length - 1]._id) { + data.map( (user) => { + const card = renderCard(user); + cardsContainer.innerHTML += card; + loadedUsers.push(user); + }); + isFetchAllowed = false; + } + } + + // Hide the loader after the content has loaded + hideLoader(); }); } } @@ -29,22 +44,53 @@ function fetchUsersData() { fetchUsersData(); // Create an html card template with the user data -function renderCard(name, userName, email, company, userId, highlight) { +function renderCard(user) { + const { + name, + username, + jobTitle, + company, + email, + languages, + skills, + _id, + location, + experience, + profilePicture, + highlight + } = user; + var template_cards = ( - '
' + - 'Card image cap' + - '
' + - '
' + name + '
' + - '

Username: ' + userName + '

' + - '

Email: ' + email + '

' + - '

Company: ' + company + '

' + - 'View Profile' + + '
' + + '' + name + ' Profile picture' + + '
' + + '

' + name + '

' + + '
' + jobTitle + '
' + + '

person ' + username + '

' + + '

email ' + email + '

' + + '

work ' + company + '

' + + '

public ' + location.city + ', ' + location.country + '

' + + 'View Profile' + '
' + '
' + // '

today ' + experience + '

' + + // '

translate ' + languages.join(', ') + '

' + + // '
' + createBadges(skills) + '
' + ); + return template_cards; } +function createBadges(skills) { + const skillBadges = []; + + skills.forEach( skill => { + skillBadges.push(`
${skill}
`); + }); + + return skillBadges.join(''); +} + function showLoader() { loader.style.display = 'block'; } diff --git a/js/search_bar.js b/js/search_bar.js index d0167f9..8bbd521 100644 --- a/js/search_bar.js +++ b/js/search_bar.js @@ -5,22 +5,37 @@ searchInput.addEventListener("keyup", function(e) { // User data separated for type const filteredResults = { name: [], + jobTitle: [], username: [], email: [], company: [] } - function filterByData(input, reference, store, user) { - if (input.toLowerCase() === reference.slice(0, input.length).toLowerCase()) { + function filterByData(input, reference, store, user, coincidence) { + if (reference.toLowerCase().includes(input.toLowerCase())) { + user.highlight.includes(coincidence) ? '' : user.highlight.push(coincidence); store.push(user); + } else { + let position = user.highlight.indexOf(coincidence); + user.highlight.includes(coincidence) && position !== -1 ? user.highlight.splice(position, 1) : ''; } } - function renderCategoryOfFilteredUsers(category, categoryName) { - category.forEach( (user) => { - const card = renderCard( user.name, user.username, user.email, user.company.name, user.id, categoryName); + function renderFilteredResults() { + const userArray = []; + + for (key in filteredResults) { + filteredResults[key].forEach( user => { + if (userArray.indexOf(user) == -1) { + userArray.push(user); + } + }); + } + + userArray.forEach( user => { + const card = renderCard(user); cardsContainer.innerHTML += card; - }) + }); } if (searchTerm) { @@ -34,21 +49,20 @@ searchInput.addEventListener("keyup", function(e) { // Save user data inside an object for future comparison const userData = { name: user.name, + jobTitle: user.jobTitle, username: user.username, email: user.email, - company: user.company.name + company: user.company } // Grab each userData property and filter it into the searchResult object for (let dataType in userData) { - filterByData(searchTerm, userData[dataType], filteredResults[dataType], user); + filterByData(searchTerm, userData[dataType], filteredResults[dataType], user, dataType); } }); // Once the users have been filtered render them into the DOM - for (let category in filteredResults) { - renderCategoryOfFilteredUsers(filteredResults[category], category); - } + renderFilteredResults(); } else { // Allow infinite scroll again @@ -56,8 +70,9 @@ searchInput.addEventListener("keyup", function(e) { // Empty the cardContainer with the filtered Results cardsContainer.innerHTML = ''; // Render the default users loaded with the initial ajax call and render them into the DOM again - loadedUsers.forEach((user, index) => { - const card = renderCard( user.name, user.username, user.email, user.company.name, index); + loadedUsers.forEach((user) => { + user.highlight = []; + const card = renderCard(user); cardsContainer.innerHTML += card; }); } diff --git a/js/user-profile.js b/js/user-profile.js new file mode 100644 index 0000000..23a9e3e --- /dev/null +++ b/js/user-profile.js @@ -0,0 +1,254 @@ +// Get the user's id from the url search parameter +const userID = window.location.search.split('=')[1]; +let currentUserInfo = ''; +// Get the user's info from the API with the user's id +fetch(`https://cv-mobile-api.herokuapp.com/api/users/${userID}`) +.then( res => res.json() ) +.then( userData => { + // Save a copy of the user info for editin mode comparison + currentUserInfo = {...userData}; + renderUSerInfo(userData); +}); + +function renderUSerInfo(user) { + // Set image src + userImg.src = user.profilePicture; + userImg.alt = user.name + ' picture'; + userImg.style.backgroundColor = '#999'; + // Print user's name + document.querySelector('#name').innerHTML = user.name; + // Print user's job title + document.querySelector('#jobTitle').innerHTML = user.jobTitle; + // Print user's username + document.querySelector('#username').innerHTML = user.username; + // Print user's email + document.querySelector('#email').innerHTML = user.email; + // Print user's website + document.querySelector('#website').innerHTML = user.website !== undefined ? user.website : ' '; + // Print user's username + document.querySelector('#company').innerHTML = user.company !== undefined ? user.company : ' '; + // Print user's experience + $('#experience').replaceWith(``); + document.querySelector('#experience').innerHTML = user.experience !== undefined ? user.experience : ' '; + // Print user's langs + $('#languages').replaceWith(``); + document.querySelector('#languages').innerHTML = user.languages.join(', '); + // Print user's location + document.querySelector('#location').innerHTML = user.location.city + ', ' + user.location.country; + // Print user's skills + document.querySelector('#skills').innerHTML = createBadges(user.skills); +} + + +function createBadges(skills) { + const skillBadges = []; + + skills.forEach( skill => { + skillBadges.push(`
${skill}
`); + }); + + return skillBadges.join(''); +} + + +// Edit mode state +let editModeStatus = false; +// Change interface icons on edit mode status +function changeEditModeStatus() { + editModeStatus = !editModeStatus; + if (editModeStatus) { + edit.classList.add('d-none'); + save.classList.remove('d-none'); + cancel.classList.remove('d-none'); + remove.classList.remove('d-none'); + } else { + edit.classList.remove('d-none'); + save.classList.add('d-none'); + cancel.classList.add('d-none'); + remove.classList.add('d-none'); + } +} +// Edit profile functionality +edit.addEventListener('click', openEditMode); + +function changeForSelect(property, options, multiple) { + const optionsArray = []; + + if (property === 'experience') { + options.map( option => optionsArray.push(returnOptionElement(option))); + } else { + options.map( option => optionsArray.push(returnOptionElement(option.label))); + } + + $(`#${property}`).replaceWith( + `` + ); + + function returnOptionElement(option) { + return ``; + } +} + +function openEditMode() { + changeEditModeStatus(); + // Enable to cancel the edits made + cancel.addEventListener('click', closeEditMode); + // Save profile changes functionality + save.addEventListener('click', saveProfileChanges); + // Remove user profile functionality + remove.addEventListener('click', removeUser); + + // Replace experience and languages for inputs of type select + changeForSelect('experience', ["- 1 year", "1 - 3 years", "3 - 5 years", "+ 5 years"], false); + + // Get all available languages from the api to create the select input dynamically + fetch(`https://cv-mobile-api.herokuapp.com/api/langs`) + .then( res => res.json() ).then(langs => { + changeForSelect('languages', langs, true); + // Make text input based editable + document.querySelectorAll('.user-info').forEach(el => { + el.addEventListener('input', handleTextChange); + + if (el.id !== 'experience' && el.id !== 'languages') { + el.setAttribute('contenteditable', true); + } + }); + }); +}; + +// Close edit mode and return user info to the initial state +function closeEditMode() { + if (editModeStatus) { + changeEditModeStatus(); + document.querySelectorAll('.user-info').forEach(el => { + el.setAttribute('contenteditable', false); + el.classList.remove('edited'); + el.style.color = ''; + }); + renderUSerInfo(currentUserInfo); + } +} + +// Grab the changed info and send it to the API +function saveProfileChanges() { + if (editModeStatus) { + document.querySelectorAll('.edited').forEach( el => { + switch (el.id) { + case 'location': + currentUserInfo[el.id] = { + city: el.textContent.split(', ')[0], + country: el.textContent.split(', ')[1], + state: currentUserInfo.location.state + } + break; + + default: + currentUserInfo[el.id] = $(el).val() || $(el).html(); + break; + } + }); + + let formData = new FormData(); + + formData.append('name', currentUserInfo.name); + formData.append('username', currentUserInfo.username); + formData.append('jobTitle', currentUserInfo.jobTitle); + formData.append('email', currentUserInfo.email); + formData.append('website', currentUserInfo.website); + formData.append('city', currentUserInfo.location.city); + formData.append('state', currentUserInfo.location.state); + formData.append('country', currentUserInfo.location.country); + formData.append('languages', currentUserInfo.languages.join(', ')); + formData.append('skills', currentUserInfo.skills.join(', ')); + formData.append('company', currentUserInfo.company); + formData.append('experience', currentUserInfo.experience); + formData.append('birthDate', '1986-02-25T00:00:00.000Z'); + formData.append('gender', 'male'); + formData.append('profilePicture', currentUserInfo.profilePicture); + + // Send the data to the server + fetch(`https://cv-mobile-api.herokuapp.com/api/users/${userID}`, { + method: 'PUT', + body: formData + }).then( res => res.json()) + .then( updatedUser => { + updatedUser.profilePicture = currentUserInfo.profilePicture; + currentUserInfo = {...updatedUser}; + closeEditMode(); + }); + } +} + +function removeUser() { + if (editModeStatus) { + fetch(`https://cv-mobile-api.herokuapp.com/api/users/${userID}`, { + method: 'DELETE' + }) + .then(data => data.json()) + .then(response => { + console.log(response); + window.location.pathname = '/index.html'; + }); + } +} + +// Handle user's info content changes +function handleTextChange(e) { + + if (editModeStatus) { + const targetName = e.target.id; + const targetContent = $(this).val() || e.target.textContent; + + switch (targetName) { + case 'languages': + // Comprobar que el array que los idiomas es igual que un array a partir del string actual + const checkArr = []; + targetContent.forEach( el => currentUserInfo.languages.includes(el) ? checkArr.push(true) : checkArr.push(false)); + + if (checkArr.length !== currentUserInfo.languages.length || checkArr.includes(false)) { + e.target.classList.add('edited'); + e.target.style.color = '#f28202'; + } else { + e.target.classList.remove('edited'); + e.target.style.color = '#05c643'; + } + break; + + case 'location': + const modifiedLocation = { + city: targetContent.split(', ')[0], + country: targetContent.split(', ')[1], + state: currentUserInfo.location.state + } + if (currentUserInfo.location.city === modifiedLocation.city && currentUserInfo.location.country === modifiedLocation.country) { + e.target.classList.remove('edited'); + } else { + e.target.classList.add('edited'); + } + break; + + case 'experience': + if (currentUserInfo.experience !== this.value) { + e.target.classList.add('edited'); + e.target.style.color = '#f28202'; + } else { + e.target.classList.remove('edited'); + e.target.style.color = '#05c643'; + } + + break; + + default: + if (currentUserInfo[targetName] !== targetContent) { + // Change the text color when the info has changed from the initial data + e.target.classList.add('edited'); + } else { + // Change the text color back to green when the info is equal to the initial data + e.target.classList.remove('edited'); + } + break; + } + } +} \ No newline at end of file