diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 00000000..01625912
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..b73a9de9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+code/token.js
diff --git a/README.md b/README.md
index 1613a3b0..883f4f73 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,24 @@
-# GitHub Tracker
+# Project GitHub Tracker
-Replace this readme with your own information about your project.
+Weekly project for Technigo's bootcamp, week 7: build a GitHub tracker (February 2022)
-Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
+## Tech stack
+
+- JavaScript
+- HTML
+- CSS
+- API
## The problem
-Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next?
+The project is to build a GitHub tracker to gather my Technigo repositories in order to practice JavaScript and API skills through GitHub API. It contains some data about the repositories like commit messages, lastest push and so on.
+
+At first I felt some stress since there was no starter code to begin with and it felt like a big mountain to climb. But in the end, I found it quite easy and interesting to work with many fetches nested inside one another. My biggest struggles were to style the charts with chart.js since the documentation wasn't always clear and to deal with the layout I chose (card with tab buttons). Even if I always want to reach more advanced goals, I didn't feel the need to add a filter or a search field since the number of repositories is quite low, and it didn't seemed a big challenge anyway.
## View it live
-Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
+Project deployed here: [Nadia Lefebvre - GitHub Tracker](https://nadialefebvre-github-tracker.netlify.app/)
+
+
+
+
diff --git a/code/chart.js b/code/chart.js
index 92e85a30..ec78f724 100644
--- a/code/chart.js
+++ b/code/chart.js
@@ -1,4 +1,125 @@
-//DOM-selector for the canvas 👇
-const ctx = document.getElementById('chart').getContext('2d')
+const reposChart = document.getElementById('reposChart').getContext('2d')
-//"Draw" the chart here 👇
+// global options for chart.js
+Chart.defaults.font.family = 'Roboto'
+Chart.defaults.font.size = 24
+Chart.defaults.color = 'white'
+
+const drawReposChart = (amount) => {
+ new Chart(reposChart, {
+ type: 'bar',
+ data: {
+ labels: ['Done', 'To do'],
+ datasets: [{
+ data: [
+ amount,
+ 19 - amount
+ ],
+ backgroundColor: [
+ '#DBE2EF',
+ '#3F72AF'
+ ]
+ }]
+ },
+ options: {
+ indexAxis: 'y', // for horizontal bar graph instead of vertical
+ scales: {
+ y: {
+ grid: {
+ display: false, // removes grid lines for y axis
+ borderColor: 'white',
+ }
+ },
+ x: {
+ display: false, // removes x axis
+ }
+ },
+ plugins: {
+ title: {
+ display: true,
+ text: 'Technigo projects',
+ },
+ legend: {
+ display: false,
+ },
+ tooltip: {
+ backgroundColor: '#F9F7F7',
+ bodyColor: '#112D4E',
+ titleColor: '#112D4E',
+ titleAlign: 'center',
+ bodyAlign: 'center',
+ titleFont: {
+ size: 12,
+ weight: '700'
+ },
+ bodyFont: {
+ size: 12,
+ weight: '700'
+ },
+ cornerRadius: 2,
+ displayColors: false
+ }
+ }
+ }
+ })
+}
+
+const drawLanguagesChart = (html, css, js, idChart) => {
+ const languagesChart = document.getElementById(idChart).getContext('2d')
+ new Chart(languagesChart, {
+ type: 'polarArea',
+ data: {
+ labels: ['HTML', 'CSS', 'JS'],
+ datasets: [{
+ data: [
+ html,
+ css,
+ js
+ ],
+ backgroundColor: [
+ '#DBE2EF',
+ '#3F72AF',
+ '#112D4E'
+ ]
+ }
+ ]
+ },
+ options: {
+ scales: {
+ r: {
+ ticks: {
+ display: false, // removes vertical numbers
+ // callback added because there is an issue with ticks, link: https://github.com/chartjs/Chart.js/issues/8092
+ callback: function (val, index) {
+ return val
+ },
+ },
+ grid: {
+ display: false // removes circular lines
+ }
+ }
+ },
+ plugins: {
+ title: {
+ display: false
+ },
+ legend: {
+ display: true,
+ position: 'top',
+ labels: {
+ color: '#112D4E',
+ usePointStyle: true,
+ pointStyle: 'rect',
+ font: {
+ size: 12
+ }
+ },
+ onClick: null, // removes on click event: not possible to strike through a label by clicking on it
+ },
+ tooltip: {
+ enabled: false,
+ }
+ }
+ }
+ })
+}
\ No newline at end of file
diff --git a/code/favicon.ico b/code/favicon.ico
new file mode 100644
index 00000000..db384e62
Binary files /dev/null and b/code/favicon.ico differ
diff --git a/code/index.html b/code/index.html
index 2fb5e0ae..40c4e8ea 100644
--- a/code/index.html
+++ b/code/index.html
@@ -1,21 +1,80 @@
+
- Project GitHub Tracker
+
+
+
+
+
+
+ Nadia Lefebvre - GitHub Tracker
+
- GitHub Tracker
- Projects:
-
+
+
-
-
+
+
+ A to Z
+ Z to A
+ Latest
+ Oldest
+
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/script.js b/code/script.js
index e69de29b..d15b0de4 100644
--- a/code/script.js
+++ b/code/script.js
@@ -0,0 +1,268 @@
+const userBox = document.getElementById('userBox')
+const reposBox = document.getElementById('reposBox')
+const sortingDropdown = document.getElementById('sortingDropdown')
+
+const username = 'nadialefebvre'
+const API_USER = `https://api.github.com/users/${username}`
+const API_REPOS = `https://api.github.com/users/${username}/repos`
+
+const API_TOKEN = TOKEN
+const options = {
+ method: 'GET',
+ headers: {
+ Authorization: `token ${API_TOKEN}`
+ }
+}
+
+// Function for the tab buttons in repo card
+const openTab = (event, tabName) => {
+ let i
+ let repoContent = event.currentTarget.parentNode.parentNode.getElementsByClassName("repo-content") // targets the content on each individual repo card
+ for (i = 0; i < repoContent.length; i++) {
+ repoContent[i].style.display = "none" // not displayed at first
+ }
+
+ let tabButton = event.currentTarget.parentNode.getElementsByClassName("tab-button")
+ // when another button is clicked, "active" class is removed from the other buttons of the same card
+ for (i = 0; i < tabButton.length; i++) {
+ tabButton[i].className = tabButton[i].className.replace(" active", "")
+ }
+
+ document.getElementById(tabName).style.display = "block"
+ event.currentTarget.className += " active" // changes the classname on click to "active"
+}
+
+// Function to create the profile part of the header with user name and picture
+// -------------------------------------------------- FETCH 1 --------------------------------------------------
+const createProfile = () => {
+ fetch(API_USER, options)
+ .then(res => res.json())
+ .then(data => {
+ userBox.innerHTML = `
+
+ ${data.name}
+ `
+ })
+}
+
+// Function to create all repo cards
+// -------------------------------------------------- FETCH 2 --------------------------------------------------
+const createRepoCard = (reposToUse = null) => {
+ fetch(API_REPOS, options)
+ .then(res => res.json())
+ .then(data => {
+ const selectSorting = () => {
+ if (sortingDropdown.value === 'Z to A') {
+ sortByNameZA(filteredRepos)
+ } else if (sortingDropdown.value === 'Latest') {
+ sortDateLatest(filteredRepos)
+ } else if (sortingDropdown.value === 'Oldest') {
+ sortDateOldest(filteredRepos)
+ } else {
+ sortByNameAZ(filteredRepos)
+ }
+ reposBox.innerHTML = ''
+ // function to create all repo cards called after sorting
+ createRepoCard(filteredRepos)
+ }
+
+ // conditionals so the repos chart from chart.js is drawn only once
+ // filtering of the repos forked from technigo
+ let filteredRepos
+ if (reposToUse === null) {
+ filteredRepos = data.filter(repo => repo.fork === true && repo.name.startsWith('project-'))
+ drawReposChart(filteredRepos.length)
+ sortingDropdown.addEventListener('change', selectSorting)
+ } else {
+ filteredRepos = reposToUse
+ }
+
+ filteredRepos.forEach(repo => {
+ // function called to set the HTML structure of each repo card filtered
+ setRepoCardStructure(repo)
+
+ document.getElementById(`${repo.name}-repoData`).innerHTML += `
+ URL: ${repo.name}
+ Default branch : ${repo.default_branch}
+ Last push: ${new Date(repo.pushed_at).toLocaleDateString('en-GB')}
+ `
+
+ // get the element with id="defaultOpen" and click on it
+ document.getElementById(`${repo.name}-defaultOpen`).click()
+
+ // functions called after filtering the right repos
+ addCommits(repo)
+ addLanguageChart(repo)
+ fetchPullRequest(repo)
+ })
+ })
+}
+
+// Function for the structure of each repo card with dynamic IDs
+const setRepoCardStructure = (repo) => {
+ reposBox.innerHTML += `
+
+
+ Data
+ Languages
+ Commits
+ Pull request
+
+
+
+
${repo.name.replace('project-', '').replace('-', ' ')}
+
+
+
+
+
${repo.name.replace('project-', '').replace('-', ' ')}
+
+
+
+
+
${repo.name.replace('project-', '').replace('-', ' ')}
+
Messages
+
+
+
+
+
+ `
+}
+
+// Function to add the commits data in the right place
+// -------------------------------------------------- FETCH 3 (inside of FETCH 2) --------------------------------------------------
+const addCommits = (repo) => {
+ fetch(`https://api.github.com/repos/${username}/${repo.name}/commits?per_page=100`, options)
+ .then(res => res.json())
+ .then(data => {
+ // I need to solve why item.author.login === username doesn't work here
+ const userCommits = data.filter((item) => item.commit.author.name === 'Nadia Lefebvre')
+
+ document.getElementById(`${repo.name}-repoData`).innerHTML += `
+ Fork commits: ${userCommits.length} (on ${data.length})
+ `
+
+ userCommits.forEach(item => {
+ document.getElementById(`${repo.name}-repoCommitMessages`).innerHTML += `
+ ${item.commit.message}
+ `
+ })
+ })
+}
+
+// Function to add the language chart in each repo card
+// -------------------------------------------------- FETCH 4 (inside of FETCH 2) --------------------------------------------------
+const addLanguageChart = (repo) => {
+ fetch(`https://api.github.com/repos/${username}/${repo.name}/languages`, options)
+ .then(res => res.json())
+ .then(languages => {
+ // variables so data will be 0 and not undefined if = 0
+ const html = languages.HTML === undefined ? 0 : languages.HTML
+ const css = languages.CSS === undefined ? 0 : languages.CSS
+ const js = languages.JavaScript === undefined ? 0 : languages.JavaScript
+
+ const idChart = `${repo.name}Chart`
+
+ document.getElementById(`${repo.name}-repoLanguages`).innerHTML += `
+
+
+
+ `
+ // function called from chart.js
+ drawLanguagesChart(html, css, js, idChart)
+ })
+}
+
+// Function to fetch the pull request for each repo card
+// -------------------------------------------------- FETCH 4 (inside of FETCH 2) --------------------------------------------------
+const fetchPullRequest = (repo) => {
+ // only 1 page of 100 PR is fetched, this will be an issue when the PR will go to page 2
+ fetch(`https://api.github.com/repos/technigo/${repo.name}/pulls?per_page=100&page=1`)
+ .then(res => res.json())
+ .then(data => {
+ fetchCodeReview(data, repo)
+ })
+}
+
+// Function to fetch the code review data for each repo card
+// -------------------------------------------------- FETCH 5 (inside of FETCH 4) --------------------------------------------------
+const fetchCodeReview = (data, repo) => {
+ const userPulls = data.filter((item) => item.user.login === username)
+ userPulls.forEach(pull => {
+ fetch(pull.review_comments_url)
+ .then(res => res.json())
+ .then(data => {
+ if (data.length) {
+ const reviewID = data[0].pull_request_review_id
+ // function called to add code review data
+ addCodeReviewData(repo, pull, reviewID)
+ }
+ })
+ })
+}
+
+
+// Function to add the code review data in each repo card
+// -------------------------------------------------- FETCH 6 (inside of FETCH 5) --------------------------------------------------
+const addCodeReviewData = (repo, pull, reviewID) => {
+ fetch(`https://api.github.com/repos/technigo/${repo.name}/pulls/${pull.number}/reviews/${reviewID}`)
+ .then(res => res.json())
+ .then(data => {
+ document.getElementById(`${repo.name}-repoPullComments`).innerHTML = `
+ Pull request with code review
+ Code review comment (if any)
+ ${data.body}
+ `
+ })
+}
+
+// Sorting functions
+const sortByNameAZ = (filteredRepos) => {
+ filteredRepos.sort(function (a, b) {
+ const nameA = a.name.toLowerCase() // ignore upper and lowercase
+ const nameB = b.name.toLowerCase() // ignore upper and lowercase
+ if (nameA < nameB) {
+ return -1
+ }
+ if (nameA > nameB) {
+ return 1
+ }
+ // names must be equal
+ return 0
+ })
+}
+
+const sortByNameZA = (filteredRepos) => {
+ filteredRepos.sort(function (a, b) {
+ const nameA = a.name.toLowerCase() // ignore upper and lowercase
+ const nameB = b.name.toLowerCase() // ignore upper and lowercase
+ if (nameA > nameB) {
+ return -1
+ }
+ if (nameA < nameB) {
+ return 1
+ }
+ // names must be equal
+ return 0
+ })
+}
+
+const sortDateLatest = (filteredRepos) => {
+ filteredRepos.sort(function (a, b) {
+ return new Date(b.created_at) - new Date(a.created_at)
+ })
+}
+
+const sortDateOldest = (filteredRepos) => {
+ filteredRepos.sort(function (a, b) {
+ return new Date(a.created_at) - new Date(b.created_at)
+ })
+}
+
+// First functions called when accessing the page
+createProfile()
+createRepoCard()
\ No newline at end of file
diff --git a/code/style.css b/code/style.css
index 7c8ad447..4776c94c 100644
--- a/code/style.css
+++ b/code/style.css
@@ -1,3 +1,183 @@
+:root {
+ --white: #ffffff;
+ --lightbackground: #f9f7f7;
+ --lightblue: #dbe2ef;
+ --mediumblue: #3f72af;
+ --darkblue: #112d4e;
+}
+
+* {
+ box-sizing: border-box;
+}
+
body {
- background: #FFECE9;
-}
\ No newline at end of file
+ font-family: "Roboto", sans-serif;
+ background-color: var(--darkblue);
+ color: var(--white);
+ margin: 0;
+}
+
+a {
+ color: var(--mediumblue);
+ font-weight: bold;
+ text-decoration: none;
+}
+
+.main-box {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.header-box {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.user-box {
+ text-align: center;
+ margin-top: 2rem;
+}
+
+.user-picture {
+ width: 12.5rem;
+ border-radius: 50%;
+}
+
+.user-box h2 {
+ margin: 0;
+}
+
+.project-title {
+ width: -webkit-fit-content;
+ width: -moz-fit-content;
+ width: fit-content;
+ padding: 1.25rem;
+ background-color: var(--lightblue);
+ color: var(--mediumblue);
+ border-radius: 2px;
+ letter-spacing: 0.1rem;
+}
+
+.repos-chart {
+ width: 18rem;
+}
+
+.sorting-dropdown {
+ display: flex;
+ align-self: center;
+ border: transparent;
+ border-radius: 2px;
+ outline: none;
+ font-size: inherit;
+ background-color: var(--lightblue);
+ color: var(--darkblue);
+ width: -webkit-fit-content;
+ width: -moz-fit-content;
+ width: fit-content;
+ text-align: center;
+ margin: 1.25rem 0;
+ padding: 0.25rem 0.75rem;
+}
+
+.repos-box {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ color: var(--darkblue);
+ padding: 1.25rem;
+}
+
+.repo-card {
+ margin: 1.25rem;
+ width: 25rem;
+}
+
+.repo-tabs {
+ float: left;
+ background-color: var(--lightblue);
+ width: 30%;
+ height: 20rem;
+}
+
+.repo-tabs button {
+ display: block;
+ background-color: inherit;
+ color: var(--darkblue);
+ padding-left: 0.4rem;
+ width: 100%;
+ height: 25%;
+ border: none;
+ outline: none;
+ text-align: left;
+ cursor: pointer;
+ transition: 0.3s;
+ font-size: 0.75rem;
+}
+
+.repo-tabs button:hover {
+ background-color: var(--lightbackground);
+}
+
+.repo-tabs button.active {
+ background-color: var(--mediumblue);
+ color: var(--white);
+}
+
+.repo-content {
+ float: left;
+ background-color: var(--lightbackground);
+ padding: 0 0.75rem;
+ width: 70%;
+ border-left: none;
+ height: 20rem;
+ overflow: auto;
+}
+
+.repo-name {
+ text-transform: capitalize;
+}
+
+.footer-box {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ -moz-column-gap: 0.3rem;
+ column-gap: 0.3rem;
+ padding: 1rem 0;
+ font-size: 1rem;
+}
+
+.footer-box .fab {
+ font-size: 1.5rem;
+}
+
+/* Media queries for tablet and desktop */
+@media only screen and (min-width: 768px) {
+ .repo-card {
+ width: 20rem;
+ }
+
+ .repo-tabs,
+ .repo-content {
+ height: 16rem;
+ }
+}
+
+@media only screen and (min-width: 1024px) {
+ .header-box {
+ flex-direction: row;
+ justify-content: space-between;
+ }
+
+ .user-box {
+ width: 18rem;
+ }
+
+ .repos-chart {
+ width: 18rem;
+ }
+}
diff --git a/screenshot.jpg b/screenshot.jpg
new file mode 100644
index 00000000..54e499a0
Binary files /dev/null and b/screenshot.jpg differ