Camper Leaderboard (React)

In this example below you will see how to do a Camper Leaderboard (React) with some HTML / CSS and Javascript

A Free Code Camp Leaderboard made with React

Thumbnail
This awesome code was written by robert77, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright robert77 ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>Camper Leaderboard (React)</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">

  <link rel='stylesheet prefetch' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css'>
<link rel='stylesheet prefetch' href='https://fonts.googleapis.com/css?family=Raleway'>
<link rel='stylesheet prefetch' href='https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css'>

      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <div id='app'></div>
  <script src='https://code.jquery.com/jquery-2.2.4.min.js'></script>
<script src='https://cdn.jsdelivr.net/react/15.4.0/react.min.js'></script>
<script src='https://cdn.jsdelivr.net/react/15.4.0/react-dom.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery.lazyload/1.9.1/jquery.lazyload.min.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/robert77/camper-leaderboard-react-GqNoge */
/*
Page Settings
*/
body {
  font-family: Raleway, sans-serif;
  background: #f2f2f2;
}

.navbar {
  background: #f2f2f2;
  margin-bottom: 0px;
}
.navbar .lead {
  font-size: 28px;
}

/*
table header
*/
.row-header:hover {
  color: #333333;
}
.row-header .number-header,
.row-header .data-header,
.row-header .user-header {
  outline: none;
  border: none;
  background: #8cd28c;
}
.row-header .user-header {
  padding-left: 30px;
}
.row-header .data-header {
  cursor: pointer;
}
.row-header .fa {
  margin-left: 5%;
}

/*
Table Content
*/
tr:hover {
  color: #eeeeee;
}
tr:hover td {
  transition: background .25s, color .25s;
  background: #550055;
}
tr:hover img {
  width: 75px;
  border-radius: 50%;
}
tr:nth-child(even) {
  background: #b6dcb6;
}
tr:nth-child(odd) {
  background: #c8dcc8;
}
tr td {
  vertical-align: middle !important;
  outline: 1px solid rgba(247, 247, 247, 0.5);
}

/* User Component */
.user {
  width: 100%;
}
.user_primary {
  width: 30%;
}
.user_image {
  transition: width .25s, border-radius .25s;
  width: 50px;
  border-radius: 10px;
}
.user_name {
  padding-left: 10px;
}

/*
for hiding footer before ajax data has loaded and for hiding fa-animations
*/
.disabled {
  display: none;
}

/*
On clicking the active data, animation to indicate this data is already active
*/
.animate {
  -webkit-transform: translate(10px) rotate(5deg);
  transform: translate(10px) rotate(5deg);
}

/* Media Queries for Small Screen */
@media screen and (max-width: 500px) {
  .navbar .lead {
    font-size: 20px;
  }

  .user-header,
  .data-header {
    font-size: 14px;
  }

  .user_name, .user_number {
    font-size: 11px;
  }
  .user_score {
    font-size: 14px;
  }
  .user_image {
    width: 40px;
  }

  .container {
    padding: 0;
  }
}


/*Downloaded from https://www.codeseek.co/robert77/camper-leaderboard-react-GqNoge */
var PropTypes = React.PropTypes;

function User(props) {
	return React.createElement(
		'tr',
		{ className: 'align-center disabled user' },
		React.createElement(
			'td',
			{ className: 'h4 text-center user_number' },
			props.number
		),
		React.createElement(
			'td',
			{ className: 'user_primary' },
			React.createElement(
				'a',
				{ href: props.url + props.username,
					className: 'user_link',
					target: 'blank',
					rel: 'noopener' },
				React.createElement('img', {
					src: props.img,
					className: 'user_image',
					alt: 'user image' })
			),
			React.createElement(
				'span',
				{
					className: 'h4 user_name' },
				props.username
			)
		),
		React.createElement(
			'td',
			null,
			React.createElement(
				'p',
				{
					className: 'h4 text-center user_score' },
				props.recent
			)
		),
		React.createElement(
			'td',
			null,
			React.createElement(
				'p',
				{
					className: 'h4 text-center user_score' },
				props.alltime
			)
		)
	);
}
User.propTypes = {
	number: PropTypes.number.isRequired,
	url: PropTypes.string.isRequired,
	img: PropTypes.string.isRequired,
	username: PropTypes.string.isRequired,
	recent: PropTypes.number.isRequired,
	allTime: PropTypes.number.isRequired
};

function Footer() {
	return React.createElement(
		'footer',
		{ className: 'footer disabled' },
		React.createElement(
			'div',
			{ className: 'container-fluid' },
			React.createElement(
				'p',
				{ className: 'lead text-center' },
				'Made by \xA0',
				React.createElement(
					'a',
					{ href: 'https://www.freecodecamp.com/robfr77',
						target: '_blank',
						rel: 'noopener' },
					'robfr77'
				)
			)
		)
	);
}

function Header() {
	return React.createElement(
		'nav',
		{ className: 'navbar navbar-default' },
		React.createElement(
			'div',
			{ className: 'container-fluid' },
			React.createElement(
				'h1',
				{ className: 'lead text-center' },
				React.createElement(
					'a',
					{ href: 'https://www.freecodecamp.com/',
						target: 'blank',
						rel: 'noopener' },
					'Free Code Camp'
				),
				'\xA0 Leaderboard'
			)
		)
	);
}

// The Body component renders the header UI for the table and the User components.
// It also uses the callback functions for loading the data on clicking the top30 or allTime headers
function Body(props) {
	return React.createElement(
		'div',
		{ className: 'table-responsive container' },
		React.createElement(
			'table',
			{ className: 'table table-bordered' },
			React.createElement(
				'thead',
				null,
				React.createElement(
					'tr',
					{ className: 'row-header' },
					React.createElement('td', { className: 'number-header' }),
					React.createElement(
						'td',
						{
							className: 'h3 user-header' },
						'User'
					),
					React.createElement(
						'td',
						{
							className: 'h3 text-center data-header',
							onClick: props.toggleTop30 },
						'Last 30',
						React.createElement('i', {
							id: 'top30_icon',
							className: 'fa fa-spinner fa-fw fa-pulse' })
					),
					React.createElement(
						'td',
						{
							className: 'h3 text-center data-header',
							onClick: props.toggleAllTime },
						'All Time',
						React.createElement('i', {
							id: 'allTime_icon',
							className: 'fa fa-spinner fa-fw fa-pulse disabled' })
					)
				)
			),
			React.createElement(
				'tbody',
				null,
				props.users.map(function (user) {
					return React.createElement(User, {
						key: props.users.indexOf(user),
						number: props.users.indexOf(user) + 1,
						img: user.img,
						alltime: user.alltime,
						recent: user.recent,
						username: user.username,
						url: 'https://www.freecodecamp.com/'
					});
				})
			)
		)
	);
}
Body.propTypes = {
	users: PropTypes.array.isRequired,
	toggleTop30: PropTypes.func.isRequired,
	toggleAllTime: PropTypes.func.isRequired

	/* The Body Container passes down the ajax request callbacks & the user data itself to the Body Component. */
	/* It is responsible for the animation of the loading state. */
	/* It reveals the footer only once the content has loaded. */
};var BodyContainer = React.createClass({
	displayName: 'BodyContainer',

	propTypes: {
		onDataToggle: PropTypes.func.isRequired,
		active: PropTypes.string.isRequired,
		top30: PropTypes.array,
		allTime: PropTypes.array
	},
	//callback functions for Body Component
	//check active set of data & animate header if section has already been selected
	//otherwise use callback from App Component to change active data
	toggleTop30: function toggleTop30() {
		if (this.props.active === 'top30') {
			var animateHeader = function animateHeader() {
				$('#top30_icon').closest('td').toggleClass('animate');
			};

			$('#top30_icon').closest('td').toggleClass('animate');
			setTimeout(animateHeader, 100);
			;
		} else {
			this.props.onDataToggle();
		}
	},
	toggleAllTime: function toggleAllTime() {
		if (this.props.active === 'allTime') {
			var animateHeader = function animateHeader() {
				$('#allTime_icon').closest('td').toggleClass('animate');
			};

			$('#allTime_icon').closest('td').toggleClass('animate');
			setTimeout(animateHeader, 100);
			;
		} else {
			this.props.onDataToggle();
		}
	},
	//toggle pulse animation on component update
	componentWillUpdate: function componentWillUpdate() {
		function toggleLoadingAnimation() {
			$('.fa-pulse').not('.disabled').toggleClass('disabled');
		}
		toggleLoadingAnimation();
		setTimeout(toggleLoadingAnimation, 500);
	},
	// On Initial Load, reveal the footer only once page content has loaded
	componentDidMount: function componentDidMount() {
		var updatePage = setInterval(revealFooter, 100);
		updatePage();
		function revealFooter() {
			if ($(document).height() > 2000) {
				$('footer').toggleClass('disabled');
				clearInterval(updatePage);
			}
		}
	},
	render: function render() {
		return React.createElement(Body, {
			users: this.props.users,
			toggleAllTime: this.toggleAllTime,
			toggleTop30: this.toggleTop30
		});
	}
});

// App Component holds the ajax requests for the top30 Data and allTime Data.
// It passes the data from these requests down via the arrays in its state.
// It also passes down whether the top30 or allTime data is currently active.
// And determines whether to make a new ajax request or use the already cached data if possible
var App = React.createClass({
	displayName: 'App',

	propTypes: {
		url: PropTypes.string.isRequired
	},
	getInitialState: function getInitialState() {
		return {
			top30: [],
			allTime: [],
			active: 'top30'
		};
	},
	getTop30Data: function getTop30Data() {
		$.ajax({
			method: 'get',
			url: this.props.url + 'recent',
			success: function (data) {
				this.setState({ top30: data });
			}.bind(this)
		});
	},
	getAllTimeData: function getAllTimeData() {
		$.ajax({
			method: 'get',
			url: this.props.url + 'alltime',
			success: function (data) {
				this.setState({ allTime: data });
			}.bind(this)
		});
	},
	//make ajax request if data is not already cached, otherwise just used the cached data
	handleDataToggle: function handleDataToggle() {
		this.state.active === 'top30' ? this.setState({ active: 'allTime' }, function () {
			if (!this.state.allTime.length) {
				this.getAllTimeData();
				$('#allTime_icon').toggleClass('disabled');
			} else {
				$('#allTime_icon').toggleClass('disabled');
			}
		}) : this.setState({ active: 'top30' }, function () {
			if (!this.state.top30.length) {
				this.getTop30Data();
			} else {
				$('#top30_icon').toggleClass('disabled');
			}
		});
	},
	//make ajax request on app load & animate loading
	componentWillMount: function componentWillMount() {
		this.getTop30Data();
		setInterval(function () {
			$('tr.disabled:first').fadeIn().toggleClass('disabled');
		}, 100);
	},
	render: function render() {
		return React.createElement(
			'main',
			null,
			React.createElement(Header, null),
			React.createElement(BodyContainer, {
				users: this.state[this.state.active],
				active: this.state.active,
				onDataToggle: this.handleDataToggle }),
			React.createElement(Footer, null)
		);
	}
});

/* Base URL to retrieve data from is passed into App component */
ReactDOM.render(React.createElement(App, { url: 'https://fcctop100.herokuapp.com/api/fccusers/top/' }), document.getElementById('app'));

Comments