Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Commit

Permalink
feat: user profile loading skeleton (#600)
Browse files Browse the repository at this point in the history
* Added back button to profile page

* Skeleton added

* used react-router Link for navigation

* feat: added lazzy loading of profile page

* fix: removed package.* from commits

* line space added

* some suggestions

* code simplified

* Update src/Components/ImageLoader.js

Co-authored-by: Krish Gupta <krishguptadev@outlook.com>

* Update src/Components/ImageLoader.js

Co-authored-by: Krish Gupta <krishguptadev@outlook.com>

* fix: reverted package*.json changes

* feat: add user profile loading skeleton (#465)

* Adding Skeleton to the User Profile when Loading

* Update src/Components/UserProfile/Social.js

* Resolve merge Conflicts

* chore: remove old Home Component

* chore: switch back to #465

* test: username

* chore(sync): revert to main

* feat: skeleton mockup

chore(prettier): format code

fix: JSX closing tag

fix: wrong var

fix: fix issues with mockup

* chore: remove duplicate file

* refactor: duplicate components

Co-authored-by: nivendha <knivendha@gmail.com>
Co-authored-by: GUNEET SINGH <guneetsingh@GUNEETs-MacBook-Air.local>
Co-authored-by: Stephen Mount <sm@ste.london>
Co-authored-by: Stephen Mount <150512+stemount@users.noreply.github.com>
Co-authored-by: Krish Gupta <krishguptadev@outlook.com>
Co-authored-by: suhail34 <suhailkna@gmail.com>
Co-authored-by: Eddie Jaoude <eddie@jaoudestudios.com>
8 people authored Oct 25, 2021

Verified

This commit was signed with the committer’s verified signature.
987Nabil Nabil Abdel-Hafeez
1 parent e68df66 commit fbac8f7
Showing 13 changed files with 216 additions and 87 deletions.
9 changes: 9 additions & 0 deletions cypress/integration/404.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Feature: Page not found

Check error messages are displayed

Scenario: Error 404 page
Given I open "404" page
And I see "Contribute on" text in section "footer"
And I see "Profile not found" text in section "main"
And I do not see "Search" text in section "main"
3 changes: 3 additions & 0 deletions cypress/integration/common/actions.js
Original file line number Diff line number Diff line change
@@ -7,6 +7,9 @@ Given('I open {string} page', (uri) => {
case 'home':
path = '/'
break
case '404':
path = '/abcdef'
break
default:
path = uri
}
4 changes: 2 additions & 2 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'

import Footer from './Components/Footer'
import Socials from './Components/Socials'
import User from './Components/UserProfile/User'
import Home from './Components/Home/Home'

function App() {
@@ -16,7 +16,7 @@ function App() {
<Router>
<Switch>
<Route path="/:username">
<Socials />
<User />
</Route>
<Route path="/">
<Home />
3 changes: 2 additions & 1 deletion src/Components/Home/Placeholders.js
Original file line number Diff line number Diff line change
@@ -10,7 +10,8 @@ function Placeholder({ list }) {
return (
<Skeleton
width="15%"
height="2.4rem" borderRadius="10px"
height="2.4rem"
borderRadius="10px"
className="p-mr-2 p-m-2"
key={`skeleton-${key}`}
/>
32 changes: 32 additions & 0 deletions src/Components/ImageLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { useEffect, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import { Skeleton } from 'primereact/skeleton'

const ImageLoader = ({ avatar, username }) => {
const imgEl = useRef(null)
const [loaded, setLoaded] = useState(false)
const onImageLoaded = () => setLoaded(true)

useEffect(() => {
const imgElCurrent = imgEl.current

if (imgElCurrent) {
imgElCurrent.addEventListener('load', onImageLoaded)
return () => imgElCurrent.removeEventListener('load', onImageLoaded)
}
}, [])

return (
<>
{!loaded && <Skeleton className="p-avatar" shape="circle" size="4rem" />}
{loaded && <img ref={imgEl} src={avatar} alt={username} />}
</>
)
}

ImageLoader.propTypes = {
avatar: PropTypes.string,
username: PropTypes.string,
}

export default ImageLoader
3 changes: 3 additions & 0 deletions src/Components/Links.js
Original file line number Diff line number Diff line change
@@ -3,7 +3,9 @@ import './Links.css'
import React from 'react'
import PropTypes from 'prop-types'
import { Link } from 'react-router-dom'

import { Button } from 'primereact/button'

import linksConfig from '../config/links.json'

function Links({ links }) {
@@ -12,6 +14,7 @@ function Links({ links }) {
function MouseOver(e, color) {
e.target.style.background = color
}

function MouseOut(e) {
e.target.style.background = ''
}
6 changes: 5 additions & 1 deletion src/Components/Milestones.js
Original file line number Diff line number Diff line change
@@ -22,7 +22,11 @@ function Milestones({ milestones }) {
)

const content = (milestone) => (
<Card title={milestone.title} subTitle={milestone.date} className="p-m-5 p-shadow-15">
<Card
title={milestone.title}
subTitle={milestone.date}
className="p-m-5 p-shadow-15"
>
{milestone.image && (
<img
src={milestone.image}
28 changes: 22 additions & 6 deletions src/Components/Profile.js
Original file line number Diff line number Diff line change
@@ -5,8 +5,11 @@ import PropTypes from 'prop-types'

import { Avatar } from 'primereact/avatar'
import { Badge } from 'primereact/badge'
import ImageLoader from './ImageLoader'

function Profile({ profile, username }) {
const { name, bio, avatar, links } = profile

function Profile({ name, bio, avatar, total, username }) {
return (
<section>
<div className="p-d-flex p-jc-center p-ai-center">
@@ -15,9 +18,14 @@ function Profile({ name, bio, avatar, total, username }) {
imageAlt={`Profile picture of ${name}`}
size="xlarge"
shape="circle"
template={<ImageLoader avatar={avatar} username={name} />}
className="p-overlay-badge"
>
<Badge value={total} severity="info" className="p-mr-2 p-mt-2" />
<Badge
value={links.length}
severity="info"
className="p-mr-2 p-mt-2"
/>
</Avatar>
<h1 className="p-m-2">{name}</h1>
<h4 className="">({username})</h4>
@@ -30,11 +38,19 @@ function Profile({ name, bio, avatar, total, username }) {
}

Profile.propTypes = {
name: PropTypes.string.isRequired,
bio: PropTypes.string.isRequired,
avatar: PropTypes.string.isRequired,
total: PropTypes.number.isRequired,
username: PropTypes.string.isRequired,
profile: PropTypes.shape({
name: PropTypes.string.isRequired,
bio: PropTypes.string.isRequired,
avatar: PropTypes.string.isRequired,
links: PropTypes.arrayOf(
PropTypes.shape({
icon: PropTypes.string,
name: PropTypes.string,
url: PropTypes.string,
}),
),
}),
}

export default Profile
77 changes: 0 additions & 77 deletions src/Components/Socials.js

This file was deleted.

16 changes: 16 additions & 0 deletions src/Components/UserProfile/ErrorPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'

function ErrorPage() {
return (
<div className="p-text-center">
<div className="flex-column">
<img src='/eddiehub_community_logo.webp' alt="image" style={{ width: '150px' }}/>
<h1>Profile not found.</h1>
<h1>If you are a new user, please consider registering at LinkFree.</h1>
<h2>Read the documendation <a href="https://github.com/EddieHubCommunity/LinkFree#readme" target="_blank" rel="noreferrer">here</a>.</h2>
</div>
</div>
)
}

export default ErrorPage
30 changes: 30 additions & 0 deletions src/Components/UserProfile/Placeholder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'

import { Skeleton } from 'primereact/skeleton'

function Placeholder() {
return (
<section>
<div className="p-d-flex p-jc-center p-ai-center">
<Skeleton className="p-avatar" shape="circle" size="4rem" />
<Skeleton
className="p-m-2"
shape="rounded"
height="30px"
width="200px"
/>
<Skeleton className="" shape="rounded" width="100px" />
</div>
<div className="p-d-flex p-jc-center w-50">
<Skeleton
className="p-mt-4"
width="300px"
height="50px"
shape="rounded"
/>
</div>
</section>
)
}

export default Placeholder
53 changes: 53 additions & 0 deletions src/Components/UserProfile/ProfilePage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Link } from 'react-router-dom'

import Profile from '../Profile'
import Links from '../Links'
import Milestones from '../Milestones'

function ProfilePage({ profile, username }) {
return (
<main>
{
<>
<Link to="/" aria-label="Go back to Home">
<i className="pi pi-arrow-left"></i>
</Link>
<Profile profile={profile} username={username} />
<Links links={profile.links} />
</>
}
{profile.milestones && <Milestones milestones={profile.milestones} />}
</main>
)
}

ProfilePage.propTypes = {
username: PropTypes.string.isRequired,
profile: PropTypes.shape({
name: PropTypes.string.isRequired,
bio: PropTypes.string.isRequired,
avatar: PropTypes.string.isRequired,
links: PropTypes.arrayOf(
PropTypes.shape({
icon: PropTypes.string,
name: PropTypes.string,
url: PropTypes.string,
}),
),
milestones: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string,
image: PropTypes.string,
date: PropTypes.string,
icon: PropTypes.string,
color: PropTypes.string,
description: PropTypes.string,
url: PropTypes.string,
}),
),
}),
}

export default ProfilePage
39 changes: 39 additions & 0 deletions src/Components/UserProfile/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { ProgressBar } from 'primereact/progressbar'

import ProfilePage from './ProfilePage'
import Placeholder from './Placeholder'
import ErrorPage from './ErrorPage'

function User() {
const [showProgress, setShowProgress] = useState(true)
const [skeleton, setskeleton] = useState(true)
const [profile, setProfile] = useState()
const [error, setError] = useState(false)
const { username } = useParams()

useEffect(() => {
fetch(`/data/${username}.json`)
.then((response) => response.json())
.then((data) => setProfile(data))
.catch(() => setError(true))
.finally(() => {
setShowProgress(false)
setTimeout(() => setskeleton(false), 500)
})
}, [username])

return (
<main>
{showProgress && <ProgressBar mode="indeterminate" />}
{skeleton && <Placeholder />}
{error && <ErrorPage />}
{!error && !skeleton && (
<ProfilePage profile={profile} username={username} />
)}
</main>
)
}

export default User

0 comments on commit fbac8f7

Please sign in to comment.