Merge pull request #12 from jedmund/profile

Redesign/fix styling for Profile
This commit is contained in:
Justin Edmund 2022-02-26 17:47:25 -08:00 committed by GitHub
commit a1f9778185
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 218 additions and 65 deletions

View file

@ -1,54 +1,82 @@
.GridRep { .GridRep {
border: 2px solid transparent;
border-radius: 6px; border-radius: 6px;
display: flex; display: flex;
flex-direction: row; flex-direction: column;
flex-shrink: 0; gap: $unit;
margin: 0 8px 8px 0; padding: $unit * 2;
padding: 4px;
height: 148px;
width: 311px;
}
.GridRep:hover { &:hover {
border: 2px solid #2360C5; background: white;
cursor: pointer; cursor: pointer;
}
.GridRep .weapon { .Grid .grid_weapons .grid_weapon {
background: white; box-shadow: inset 0 0 1px $grey-70;
border-radius: 4px; }
} }
.GridRep .grid_mainhand { .Grid {
flex-shrink: 0; display: flex;
height: 136px; flex-direction: row;
width: 65px; flex-shrink: 0;
margin-right: 8px;
}
.GridRep .grid_weapons { .grid_mainhand {
list-style: none; margin-right: $unit;
margin: 0; height: 139px;
padding: 0; width: auto;
} }
.GridRep .grid_weapon { .grid_weapons {
background: white; display: grid;
border-radius: 4px; grid-template-columns: 1fr 1fr 1fr;
float: left; grid-template-rows: 1fr 1fr 1fr;
margin: 0 8px 8px 0; gap: $unit;
height: 40px; margin: 0;
width: 70px; padding: 0;
} width: fit-content;
}
.GridRep .grid_weapon:nth-child(3n+3) { .grid_weapon {
margin-right: 0; background: white;
} border-radius: 4px;
float: left;
height: 40px;
width: 70px;
}
.grid_mainhand img[src*="jpg"],
.grid_weapon img[src*="jpg"] {
border-radius: 4px;
width: 100%;
height: 100%;
}
}
.GridRep .grid_mainhand img[src*="jpg"], .Details {
.GridRep .grid_weapon img[src*="jpg"] { display: flex;
border-radius: 4px; flex-direction: column;
width: 100%; gap: $unit / 2;
height: 100%;
} h2 {
color: $grey-00;
font-size: $font-regular;
&.empty {
color: $grey-50;
}
}
.bottom {
display: flex;
flex-direction: row;
}
.raid, time {
color: $grey-50;
font-size: $font-small;
}
.raid {
flex-grow: 1;
}
}
}

View file

@ -1,19 +1,37 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import classNames from 'classnames'
import { formatTimeAgo } from '~utils/timeAgo'
import './index.scss' import './index.scss'
interface Props { interface Props {
shortcode: string shortcode: string
name: string
raid: Raid
grid: GridWeapon[] grid: GridWeapon[]
updatedAt: Date
onClick: (shortcode: string) => void onClick: (shortcode: string) => void
} }
const GridRep = (props: Props) => { const GridRep = (props: Props) => {
console.log(props)
const numWeapons: number = 9 const numWeapons: number = 9
const [mainhand, setMainhand] = useState<Weapon>() const [mainhand, setMainhand] = useState<Weapon>()
const [weapons, setWeapons] = useState<GridArray<Weapon>>({}) const [weapons, setWeapons] = useState<GridArray<Weapon>>({})
const titleClass = classNames({
'empty': !props.name
})
const raidClass = classNames({
'raid': true,
'empty': !props.raid
})
useEffect(() => { useEffect(() => {
const newWeapons = Array(numWeapons) const newWeapons = Array(numWeapons)
@ -43,21 +61,31 @@ const GridRep = (props: Props) => {
return ( return (
<div className="GridRep" onClick={navigate}> <div className="GridRep" onClick={navigate}>
<div className="weapon grid_mainhand"> <div className="Details">
{generateMainhandImage()} <h2 className={titleClass}>{ (props.name) ? props.name : 'Untitled' }</h2>
<div className="bottom">
<div className={raidClass}>{ (props.raid) ? props.raid.name.en : 'No raid set' }</div>
<time className="last-updated" dateTime={props.updatedAt.toISOString()}>{formatTimeAgo(props.updatedAt, 'en-us')}</time>
</div>
</div> </div>
<ul className="grid_weapons"> <div className="Grid">
{ <div className="weapon grid_mainhand">
Array.from(Array(numWeapons)).map((x, i) => { {generateMainhandImage()}
return ( </div>
<li key={`${props.shortcode}-${i}`} className="grid_weapon">
{generateGridImage(i)} <ul className="grid_weapons">
</li> {
) Array.from(Array(numWeapons)).map((x, i) => {
}) return (
} <li key={`${props.shortcode}-${i}`} className="grid_weapon">
</ul> {generateGridImage(i)}
</li>
)
})
}
</ul>
</div>
</div> </div>
) )
} }

View file

@ -1,6 +1,7 @@
.GridRepCollection { .GridRepCollection {
display: flex; display: grid;
flex-wrap: wrap; grid-template-columns: auto auto auto;
justify-content: center; margin: 0 auto;
padding: 0; padding: 0;
width: fit-content;
} }

View file

@ -1,4 +1,4 @@
.Details { .PartyDetails {
display: none; // This breaks transition, find a workaround display: none; // This breaks transition, find a workaround
opacity: 0; opacity: 0;
margin: 0 auto; margin: 0 auto;

View file

@ -24,13 +24,13 @@ const PartyDetails = (props: Props) => {
const raidSelect = React.createRef<HTMLSelectElement>() const raidSelect = React.createRef<HTMLSelectElement>()
const readOnlyClasses = classNames({ const readOnlyClasses = classNames({
'Details': true, 'PartyDetails': true,
'ReadOnly': true, 'ReadOnly': true,
'Visible': !appSnapshot.party.detailsVisible 'Visible': !appSnapshot.party.detailsVisible
}) })
const editableClasses = classNames({ const editableClasses = classNames({
'Details': true, 'PartyDetails': true,
'Editable': true, 'Editable': true,
'Visible': appSnapshot.party.detailsVisible 'Visible': appSnapshot.party.detailsVisible
}) })

View file

@ -0,0 +1,32 @@
#ProfileHeader {
align-items: center;
background: white;
border-radius: $unit;
display: flex;
margin: 0 auto;
margin-bottom: $unit * 5;
max-width: $unit * 52;
padding: ($unit * 3) ($unit * 5);
h1 {
font-size: $font-xxlarge;
font-weight: $normal;
flex-grow: 1;
text-align: left;
}
img {
$diameter: 120px;
border-radius: $diameter / 2;
height: $diameter;
width: $diameter;
&.gran {
background-color: #CEE7FE;
}
&.djeeta {
background-color: #FFE1FE;
}
}
}

View file

@ -0,0 +1,28 @@
import React, { useState } from 'react'
import { useSnapshot } from 'valtio'
import { appState } from '~utils/appState'
import './index.scss'
// Props
interface Props {
username: string
gender: boolean
}
const ProfileHeader = (props: Props) => {
return (
<section id="ProfileHeader">
<h1>{props.username}</h1>
<img
alt="Gran"
className="gran"
srcSet="/profile/gran.png,
/profile/gran@2x.png 2x"
src="/profile/gran.png" />
</section>
)
}
export default ProfileHeader

View file

@ -3,6 +3,7 @@ import { useRouter } from 'next/router'
import api from '~utils/api' import api from '~utils/api'
import ProfileHeader from '~components/ProfileHeader'
import GridRep from '~components/GridRep' import GridRep from '~components/GridRep'
import GridRepCollection from '~components/GridRepCollection' import GridRepCollection from '~components/GridRepCollection'
@ -14,8 +15,11 @@ interface User {
interface Party { interface Party {
id: string id: string
name: string
raid: Raid
shortcode: string shortcode: string
weapons: GridWeapon[] weapons: GridWeapon[]
updated_at: string
} }
const ProfileRoute: React.FC = () => { const ProfileRoute: React.FC = () => {
@ -44,7 +48,9 @@ const ProfileRoute: React.FC = () => {
username: response.data.user.username, username: response.data.user.username,
granblueId: response.data.user.granblue_id granblueId: response.data.user.granblue_id
}) })
setParties(response.data.user.parties)
const parties: Party[] = response.data.user.parties
setParties(parties.sort((a, b) => (a.updated_at > b.updated_at) ? -1 : 1))
}) })
.then(() => { .then(() => {
setFound(true) setFound(true)
@ -65,7 +71,7 @@ const ProfileRoute: React.FC = () => {
const content = (parties && parties.length > 0) ? renderGrids() : renderNoGrids() const content = (parties && parties.length > 0) ? renderGrids() : renderNoGrids()
return ( return (
<div> <div>
<h1>{user.username}</h1> <ProfileHeader username={user.username} gender={true} />
{content} {content}
</div> </div>
) )
@ -82,6 +88,9 @@ const ProfileRoute: React.FC = () => {
parties.map((party, i) => { parties.map((party, i) => {
return <GridRep return <GridRep
shortcode={party.shortcode} shortcode={party.shortcode}
name={party.name}
updatedAt={new Date(party.updated_at)}
raid={party.raid}
grid={party.weapons} grid={party.weapons}
key={`party-${i}`} key={`party-${i}`}
onClick={goTo} onClick={goTo}

BIN
public/profile/djeeta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
public/profile/gran.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
public/profile/gran@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -63,6 +63,7 @@ $font-button: 15px;
$font-regular: 16px; $font-regular: 16px;
$font-large: 21px; $font-large: 21px;
$font-xlarge: 24px; $font-xlarge: 24px;
$font-xxlarge: 28px;
// Scale factors // Scale factors
$scale-wide: scale(1.05, 1.05); $scale-wide: scale(1.05, 1.05);

26
utils/timeAgo.tsx Normal file
View file

@ -0,0 +1,26 @@
const DIVISIONS = [
{ amount: 60, name: 'seconds' },
{ amount: 60, name: 'minutes' },
{ amount: 24, name: 'hours' },
{ amount: 7, name: 'days' },
{ amount: 4.34524, name: 'weeks' },
{ amount: 12, name: 'months' },
{ amount: Number.POSITIVE_INFINITY, name: 'years' }
]
export function formatTimeAgo(date: Date, locale: string = 'en-us') {
const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' })
let duration = (date.getTime() - new Date().getTime()) / 1000
for (let i = 0; i <= DIVISIONS.length; i++) {
const division = DIVISIONS[i]
if (Math.abs(duration) < division.amount) {
return formatter.format(Math.round(duration), division.name)
}
duration /= division.amount
}
}