Merge pull request #74 from jedmund/fix-media-queries

Fix media queries
This commit is contained in:
Justin Edmund 2022-12-27 19:22:17 -08:00 committed by GitHub
commit b705bf7765
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 449 additions and 363 deletions

View file

@ -10,7 +10,8 @@
gap: 6px; gap: 6px;
&:hover, &:hover,
&.Blended:hover { &.Blended:hover,
&.Blended.Active {
background: var(--button-bg-hover); background: var(--button-bg-hover);
cursor: pointer; cursor: pointer;
color: var(--button-text-hover); color: var(--button-text-hover);

View file

@ -12,7 +12,7 @@
padding: 0; padding: 0;
max-width: 761px; max-width: 761px;
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
} }
@ -20,7 +20,7 @@
& > * { & > * {
margin-right: $unit * 3; margin-right: $unit * 3;
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
margin-right: inherit; margin-right: inherit;
} }
} }

View file

@ -56,7 +56,7 @@
height: auto; height: auto;
width: 131px; width: 131px;
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
width: 17vw; width: 17vw;
} }

View file

@ -22,7 +22,7 @@
text-decoration: underline; text-decoration: underline;
} }
@media (max-width: $phone) { @include breakpoint(phone) {
min-width: inherit; min-width: inherit;
min-height: inherit; min-height: inherit;
width: 100%; width: 100%;

View file

@ -10,7 +10,7 @@
position: relative; position: relative;
left: 9px; left: 9px;
@media (max-width: $medium-screen) { @include breakpoint(phone) {
left: auto; left: auto;
max-width: auto; max-width: auto;
width: 100%; width: 100%;

View file

@ -10,7 +10,7 @@
position: relative; position: relative;
left: 8px; left: 8px;
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
left: auto; left: auto;
max-width: auto; max-width: auto;
width: 100%; width: 100%;

View file

@ -15,7 +15,7 @@
width: 100%; width: 100%;
max-width: 996px; max-width: 996px;
@media (max-width: $tablet) { @include breakpoint(tablet) {
position: static; position: static;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
@ -29,7 +29,7 @@
gap: $unit; gap: $unit;
width: auto; width: auto;
@media (max-width: $tablet) { @include breakpoint(tablet) {
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
} }
@ -64,7 +64,7 @@
background-color: var(--select-contained-bg-hover); background-color: var(--select-contained-bg-hover);
} }
@media (max-width: $tablet) { @include breakpoint(tablet) {
width: 100%; width: 100%;
max-width: inherit; max-width: inherit;
text-align: center; text-align: center;

View file

@ -2,11 +2,12 @@
aspect-ratio: 3/2; aspect-ratio: 3/2;
border-radius: $card-corner; border-radius: $card-corner;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: grid;
flex-direction: column; grid-template-rows: 1fr 1fr;
gap: $unit; gap: $unit;
padding: $unit-2x; padding: $unit-2x;
max-width: 320px; min-width: 320px;
width: 100%;
&:hover { &:hover {
background: var(--grid-rep-hover); background: var(--grid-rep-hover);
@ -20,48 +21,58 @@
cursor: pointer; cursor: pointer;
} }
.Grid .weapon { .Grid .Weapon {
box-shadow: inset 0 0 0 1px var(--grid-border-color); box-shadow: inset 0 0 0 1px var(--grid-border-color);
} }
@include breakpoint(phone) {
background: inherit;
.Grid .Weapon {
box-shadow: none;
}
}
} }
.Grid { & > .Grid {
display: flex; aspect-ratio: 2/1;
flex-direction: row; display: grid;
flex-shrink: 0; grid-template-columns: 1fr 3fr; /* left column takes up 1 fraction, right column takes up 3 fractions */
gap: $unit; grid-template-rows: repeat(
3,
1fr
); /* create 3 rows, each taking up 1 fraction */
grid-gap: $unit; /* add a gap of 8px between grid items */
.weapon { .Weapon {
background: var(--card-bg); background: var(--card-bg);
border-radius: 4px; border-radius: 4px;
} }
.grid_mainhand { .Mainhand.Weapon {
$d: 64px; grid-column: 1 / 2; /* spans one column */
aspect-ratio: 200 / 418;
height: auto;
max-width: $d;
min-width: $d;
} }
.grid_weapons { .GridWeapons {
$p: 29.5%; display: grid; /* make the right-images container a grid */
box-sizing: border-box; grid-template-columns: repeat(
display: grid; 3,
grid-template-columns: fit-content($p) fit-content($p) fit-content($p); 1fr
grid-template-rows: fit-content($p) fit-content($p) fit-content($p); ); /* create 3 columns, each taking up 1 fraction */
grid-template-rows: repeat(
3,
1fr
); /* create 3 rows, each taking up 1 fraction */
gap: $unit; gap: $unit;
margin: 0;
padding: 0;
width: 100%;
} }
.grid_weapon { .Grid.Weapon {
aspect-ratio: 160 / 92; aspect-ratio: 160 / 92;
display: grid;
} }
.grid_mainhand img[src*='jpg'], .Mainhand.Weapon img[src*='jpg'],
.grid_weapon img[src*='jpg'] { .Grid.Weapon img[src*='jpg'] {
border-radius: 4px; border-radius: 4px;
width: 100%; width: 100%;
height: 100%; height: 100%;

View file

@ -212,15 +212,12 @@ const GridRep = (props: Props) => {
<a className="GridRep"> <a className="GridRep">
{props.displayUser ? detailsWithUsername : details} {props.displayUser ? detailsWithUsername : details}
<div className="Grid"> <div className="Grid">
<div className="weapon grid_mainhand">{generateMainhandImage()}</div> <div className="Mainhand Weapon">{generateMainhandImage()}</div>
<ul className="grid_weapons"> <ul className="GridWeapons">
{Array.from(Array(numWeapons)).map((x, i) => { {Array.from(Array(numWeapons)).map((x, i) => {
return ( return (
<li <li key={`${props.shortcode}-${i}`} className="Grid Weapon">
key={`${props.shortcode}-${i}`}
className="weapon grid_weapon"
>
{generateGridImage(i)} {generateGridImage(i)}
</li> </li>
) )

View file

@ -1,21 +1,20 @@
.GridRepCollection { .GridRepCollection {
box-sizing: border-box;
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
margin: 0 auto; margin: 0 auto;
padding: 0; padding: 0;
transition: opacity 0.14s ease-in-out; transition: opacity 0.14s ease-in-out;
justify-items: center; justify-items: center;
// width: fit-content; width: 100%;
width: auto;
max-width: 996px; max-width: 996px;
@media (max-width: $tablet) { @include breakpoint(tablet) {
grid-template-columns: minmax(320px, 1fr); grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
max-width: inherit; max-width: inherit;
width: 100%;
} }
@media (max-width: $phone) { @include breakpoint(phone) {
grid-template-columns: 1fr; grid-template-columns: 1fr;
max-width: inherit; max-width: inherit;
width: 100%; width: 100%;

View file

@ -1,23 +1,24 @@
.Header { #Header {
display: flex; display: flex;
flex-direction: row;
margin-bottom: $unit; margin-bottom: $unit;
justify-content: space-between;
width: 100%; width: 100%;
&.bottom { #Right > div {
position: sticky;
bottom: $unit * 2;
}
#right > div {
display: flex; display: flex;
gap: 8px; gap: $unit;
} }
.dropdown { #DropdownWrapper {
display: inline-block; display: inline-block;
position: relative;
padding-bottom: $unit; padding-bottom: $unit;
&:hover .Menu,
.Menu.open {
display: block;
}
&:hover { &:hover {
padding-right: $unit-4x; padding-right: $unit-4x;
@ -25,18 +26,10 @@
background: var(--button-bg-hover); background: var(--button-bg-hover);
color: var(--button-text-hover); color: var(--button-text-hover);
} }
.Menu {
display: block;
}
} }
} }
.push { @include breakpoint(phone) {
margin-left: auto;
}
@media (max-width: $phone) {
.Button .Text { .Button .Text {
display: none; display: none;
} }

View file

@ -1,19 +1,189 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import { useSnapshot } from 'valtio'
import { deleteCookie } from 'cookies-next'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import clonedeep from 'lodash.clonedeep'
import api from '~utils/api'
import { accountState, initialAccountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState'
import Button from '~components/Button'
import HeaderMenu from '~components/HeaderMenu'
import AddIcon from '~public/icons/Add.svg'
import LinkIcon from '~public/icons/Link.svg'
import MenuIcon from '~public/icons/Menu.svg'
import SaveIcon from '~public/icons/Save.svg'
import classNames from 'classnames'
import './index.scss' import './index.scss'
interface Props { const Header = () => {
position: 'top' | 'bottom' // Localization
left: JSX.Element const { t } = useTranslation('common')
right: JSX.Element
} // Router
const router = useRouter()
// State management
const [open, setOpen] = useState(false)
// Snapshots
const { account } = useSnapshot(accountState)
const { party } = useSnapshot(appState)
function menuButtonClicked() {
setOpen(!open)
}
function onClickOutsideMenu() {
setOpen(false)
}
function copyToClipboard() {
const el = document.createElement('input')
el.value = window.location.href
el.id = 'url-input'
document.body.appendChild(el)
el.select()
document.execCommand('copy')
el.remove()
}
function newParty() {
// Push the root URL
router.push('/')
// Clean state
const resetState = clonedeep(initialAppState)
Object.keys(resetState).forEach((key) => {
appState[key] = resetState[key]
})
// Set party to be editable
appState.party.editable = true
}
function logout() {
deleteCookie('account')
deleteCookie('user')
// Clean state
const resetState = clonedeep(initialAccountState)
Object.keys(resetState).forEach((key) => {
if (key !== 'language') accountState[key] = resetState[key]
})
if (router.route != '/new') appState.party.editable = false
router.push('/')
return false
}
function toggleFavorite() {
if (party.favorited) unsaveFavorite()
else saveFavorite()
}
function saveFavorite() {
if (party.id)
api.saveTeam({ id: party.id }).then((response) => {
if (response.status == 201) appState.party.favorited = true
})
else console.error('Failed to save team: No party ID')
}
function unsaveFavorite() {
if (party.id)
api.unsaveTeam({ id: party.id }).then((response) => {
if (response.status == 200) appState.party.favorited = false
})
else console.error('Failed to unsave team: No party ID')
}
const copyButton = () => {
if (router.route === '/p/[party]')
return (
<Button
accessoryIcon={<LinkIcon className="stroke" />}
blended={true}
text={t('buttons.copy')}
onClick={copyToClipboard}
/>
)
}
const leftNav = () => {
return (
<div id="DropdownWrapper">
<Button
accessoryIcon={<MenuIcon />}
className={classNames({ Active: open })}
blended={true}
text={t('buttons.menu')}
onClick={menuButtonClicked}
/>
<HeaderMenu
authenticated={account.authorized}
open={open}
username={account.user?.username}
onClickOutside={onClickOutsideMenu}
logout={logout}
/>
</div>
)
}
const saveButton = () => {
if (party.favorited)
return (
<Button
accessoryIcon={<SaveIcon />}
blended={true}
text="Saved"
onClick={toggleFavorite}
/>
)
else
return (
<Button
accessoryIcon={<SaveIcon />}
blended={true}
text="Save"
onClick={toggleFavorite}
/>
)
}
const rightNav = () => {
return (
<div>
{router.route === '/p/[party]' &&
account.user &&
(!party.user || party.user.id !== account.user.id)
? saveButton()
: ''}
{copyButton()}
<Button
accessoryIcon={<AddIcon className="Add" />}
blended={true}
text={t('buttons.new')}
onClick={newParty}
/>
</div>
)
}
const Header = (props: Props) => {
return ( return (
<nav className={`Header ${props.position}`}> <nav id="Header">
<div id="left">{props.left}</div> <div id="Left">{leftNav()}</div>
<div className="push" /> <div id="Right">{rightNav()}</div>
<div id="right">{props.right}</div>
</nav> </nav>
) )
} }

View file

@ -1,12 +1,18 @@
.Menu { .Menu {
background: var(--menu-bg); background: var(--menu-bg);
border-radius: 6px; border-radius: 6px;
box-sizing: border-box;
display: none; display: none;
min-width: 220px; min-width: 220px;
position: absolute; position: absolute;
top: $unit * 5.75; // This shouldn't be hardcoded. How to calculate it? top: $unit-8x; // This shouldn't be hardcoded. How to calculate it?
// Also, add space that doesn't make the menu disappear if you move your mouse slowly // Also, add space that doesn't make the menu disappear if you move your mouse slowly
z-index: 10; z-index: 10;
@include breakpoint(phone) {
left: $unit-2x;
right: $unit-2x;
}
} }
.MenuItem { .MenuItem {
@ -29,6 +35,16 @@
color: var(--text-primary); color: var(--text-primary);
} }
} }
@include breakpoint(phone) {
background: inherit;
color: inherit;
cursor: default;
a {
color: inherit;
}
}
} }
&.profile > div { &.profile > div {

View file

@ -1,7 +1,9 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { getCookie, setCookie } from 'cookies-next'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { setCookie } from 'cookies-next'
import classNames from 'classnames'
import retrieveCookies from '~utils/retrieveCookies'
import Link from 'next/link' import Link from 'next/link'
import * as Switch from '@radix-ui/react-switch' import * as Switch from '@radix-ui/react-switch'
@ -14,36 +16,52 @@ import LoginModal from '~components/LoginModal'
import SignupModal from '~components/SignupModal' import SignupModal from '~components/SignupModal'
import './index.scss' import './index.scss'
import { accountState } from '~utils/accountState'
interface Props { interface Props {
authenticated: boolean authenticated: boolean
open: boolean
username?: string username?: string
onClickOutside: () => void
logout?: () => void logout?: () => void
} }
const HeaderMenu = (props: Props) => { const HeaderMenu = (props: Props) => {
// Setup
const router = useRouter() const router = useRouter()
const data: GranblueCookie | undefined = retrieveCookies()
const { t } = useTranslation('common') const { t } = useTranslation('common')
const accountCookie = getCookie('account') // Refs
const accountData: AccountCookie = accountCookie const ref: React.RefObject<HTMLDivElement> = React.createRef()
? JSON.parse(accountCookie as string)
: null
const userCookie = getCookie('user') useEffect(() => {
const userData: UserCookie = userCookie const handleClickOutside = (event: Event) => {
? JSON.parse(userCookie as string) const target = event.target instanceof Element ? event.target : null
: null const isButton = target && target.closest('.Button.Active')
const localeCookie = getCookie('NEXT_LOCALE') if (
ref.current &&
target &&
!ref.current.contains(target) &&
!isButton &&
props.open
) {
props.onClickOutside()
}
}
document.addEventListener('click', handleClickOutside, true)
return () => {
document.removeEventListener('click', handleClickOutside, true)
}
}, [props.onClickOutside])
const [checked, setChecked] = useState(false) const [checked, setChecked] = useState(false)
useEffect(() => { useEffect(() => {
const locale = localeCookie const locale = data?.locale
setChecked(locale === 'ja' ? true : false) setChecked(locale === 'ja' ? true : false)
}, [localeCookie]) }, [data?.locale])
function handleCheckedChange(value: boolean) { function handleCheckedChange(value: boolean) {
const language = value ? 'ja' : 'en' const language = value ? 'ja' : 'en'
@ -51,66 +69,70 @@ const HeaderMenu = (props: Props) => {
router.push(router.asPath, undefined, { locale: language }) router.push(router.asPath, undefined, { locale: language })
} }
const menuClasses = classNames({
Menu: true,
auth: props.authenticated,
open: props.open,
})
function authItems() { function authItems() {
return ( return (
<nav> <ul className={menuClasses}>
<ul className="Menu auth"> <div className="MenuGroup">
<div className="MenuGroup"> <li className="MenuItem profile">
<li className="MenuItem profile"> <Link href={`/${data?.account.username}` || ''} passHref>
<Link href={`/${accountData.username}` || ''} passHref>
<div>
<span>{accountData.username}</span>
<img
alt={userData.picture}
className={`profile ${accountState.account.user?.element}`}
srcSet={`/profile/${accountState.account.user?.picture}.png,
/profile/${userData.picture}@2x.png 2x`}
src={`/profile/${userData.picture}.png`}
/>
</div>
</Link>
</li>
<li className="MenuItem">
<Link href={`/saved` || ''}>{t('menu.saved')}</Link>
</li>
</div>
<div className="MenuGroup">
<li className="MenuItem">
<Link href="/teams">{t('menu.teams')}</Link>
</li>
<li className="MenuItem disabled">
<div> <div>
<span>{t('menu.guides')}</span> <span>{data?.account.username}</span>
<i className="tag">{t('coming_soon')}</i> <img
alt={data?.user.picture}
className={`profile ${data?.user.element}`}
srcSet={`/profile/${data?.user.picture}.png,
/profile/${data?.user.picture}@2x.png 2x`}
src={`/profile/${data?.user.picture}.png`}
/>
</div> </div>
</li> </Link>
</div> </li>
<div className="MenuGroup"> <li className="MenuItem">
<AboutModal /> <Link href={`/saved` || ''}>{t('menu.saved')}</Link>
<ChangelogModal /> </li>
<RoadmapModal /> </div>
</div> <div className="MenuGroup">
<div className="MenuGroup"> <li className="MenuItem">
<AccountModal <Link href="/teams">{t('menu.teams')}</Link>
username={accountState.account.user?.username} </li>
picture={accountState.account.user?.picture}
gender={accountState.account.user?.gender} <li className="MenuItem disabled">
language={accountState.account.user?.language} <div>
theme={accountState.account.user?.theme} <span>{t('menu.guides')}</span>
/> <i className="tag">{t('coming_soon')}</i>
<li className="MenuItem" onClick={props.logout}> </div>
<span>{t('menu.logout')}</span> </li>
</li> </div>
</div> <div className="MenuGroup">
</ul> <AboutModal />
</nav> <ChangelogModal />
<RoadmapModal />
</div>
<div className="MenuGroup">
<AccountModal
username={data?.account.username}
picture={data?.user.picture}
gender={data?.user.gender}
language={data?.user.language}
theme={data?.user.theme}
/>
<li className="MenuItem" onClick={props.logout}>
<span>{t('menu.logout')}</span>
</li>
</div>
</ul>
) )
} }
function unauthItems() { function unauthItems() {
return ( return (
<ul className="Menu unauth"> <ul className={menuClasses}>
<div className="MenuGroup"> <div className="MenuGroup">
<li className="MenuItem language"> <li className="MenuItem language">
<span>{t('menu.language')}</span> <span>{t('menu.language')}</span>
@ -150,7 +172,9 @@ const HeaderMenu = (props: Props) => {
) )
} }
return props.authenticated ? authItems() : unauthItems() return (
<nav ref={ref}>{props.authenticated ? authItems() : unauthItems()}</nav>
)
} }
export default HeaderMenu export default HeaderMenu

View file

@ -3,7 +3,7 @@
display: flex; display: flex;
margin-bottom: $unit * 3; margin-bottom: $unit * 3;
@media (max-width: $phone) { @include breakpoint(phone) {
flex-direction: column; flex-direction: column;
gap: $unit; gap: $unit;
} }
@ -56,7 +56,7 @@
width: $width; width: $width;
transition: box-shadow 0.15s ease-in-out; transition: box-shadow 0.15s ease-in-out;
@media (max-width: $phone) { @include breakpoint(phone) {
aspect-ratio: 16/9; aspect-ratio: 16/9;
margin: 0; margin: 0;
width: inherit; width: inherit;

View file

@ -1,5 +1,5 @@
import type { ReactElement } from 'react' import type { ReactElement } from 'react'
import TopHeader from '~components/TopHeader' import TopHeader from '~components/Header'
interface Props { interface Props {
children: ReactElement children: ReactElement

View file

@ -3,7 +3,7 @@
flex-direction: column; flex-direction: column;
margin-top: $unit-4x; margin-top: $unit-4x;
@media (max-width: $phone) { @include breakpoint(phone) {
padding: 0 $unit; padding: 0 $unit;
} }
@ -30,7 +30,7 @@
width: 100%; width: 100%;
textarea { textarea {
min-height: $unit * 20; min-height: $unit * 22;
width: 100%; width: 100%;
} }
} }
@ -86,7 +86,7 @@
width: 60%; width: 60%;
height: 60%; height: 60%;
@media (max-width: $tablet) { @include breakpoint(tablet) {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }

View file

@ -9,7 +9,7 @@
max-width: 760px; max-width: 760px;
position: relative; position: relative;
@media (max-width: $phone) { @include breakpoint(phone) {
gap: $unit; gap: $unit;
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
@ -20,16 +20,16 @@
&.Editable { &.Editable {
justify-content: flex-start; justify-content: flex-start;
} }
}
.SegmentedControl { .SegmentedControl {
flex-grow: 1;
@include breakpoint(phone) {
flex-grow: 1; flex-grow: 1;
width: 100%;
@media (max-width: $phone) { display: grid;
flex-grow: 1; grid-template-columns: auto auto auto;
width: 100%;
display: grid;
grid-template-columns: auto auto auto;
}
} }
} }
} }
@ -44,7 +44,7 @@
position: absolute; position: absolute;
right: 0px; right: 0px;
@media (max-width: $phone) { @include breakpoint(phone) {
position: static; position: static;
.Text { .Text {

View file

@ -8,7 +8,7 @@
gap: 0; gap: 0;
padding: 0; padding: 0;
@media (max-width: $phone) { @include breakpoint(phone) {
min-width: inherit; min-width: inherit;
min-height: inherit; min-height: inherit;
width: 96%; // is this even right width: 96%; // is this even right
@ -68,7 +68,7 @@
padding: 0 ($unit * 1.5); padding: 0 ($unit * 1.5);
overflow-y: scroll; overflow-y: scroll;
@media (max-width: $phone) { @include breakpoint(phone) {
max-height: inherit; max-height: inherit;
} }

View file

@ -34,7 +34,7 @@
} }
} }
@media (max-width: $phone) { @include breakpoint(phone) {
min-width: initial; min-width: initial;
width: 100%; width: 100%;
} }

View file

@ -14,7 +14,7 @@
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
@media (max-width: $phone) { @include breakpoint(phone) {
&.Friend { &.Friend {
max-width: 78px; max-width: 78px;
} }

View file

@ -9,7 +9,7 @@
width: 182px; width: 182px;
height: auto; height: auto;
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
width: 20.3vw; width: 20.3vw;
} }
} }
@ -19,7 +19,7 @@
// min-height: 141px; // min-height: 141px;
min-height: 180px; min-height: 180px;
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
min-height: 16.5vw; min-height: 16.5vw;
} }
@ -29,7 +29,7 @@
width: 148px; width: 148px;
height: auto; height: auto;
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
width: 20vw; width: 20vw;
} }
} }

View file

@ -1,180 +0,0 @@
import React from 'react'
import { useSnapshot } from 'valtio'
import { getCookie, deleteCookie } from 'cookies-next'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import clonedeep from 'lodash.clonedeep'
import api from '~utils/api'
import { accountState, initialAccountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState'
import Header from '~components/Header'
import Button from '~components/Button'
import HeaderMenu from '~components/HeaderMenu'
import AddIcon from '~public/icons/Add.svg'
import LinkIcon from '~public/icons/Link.svg'
import MenuIcon from '~public/icons/Menu.svg'
import SaveIcon from '~public/icons/Save.svg'
const TopHeader = () => {
const { t } = useTranslation('common')
// Cookies
const accountCookie = getCookie('account')
const userCookie = getCookie('user')
const headers = {}
// accountCookies.account != null
// ? {
// Authorization: `Bearer ${accountCookies.account.access_token}`,
// }
// : {}
const { account } = useSnapshot(accountState)
const { party } = useSnapshot(appState)
const router = useRouter()
function copyToClipboard() {
const el = document.createElement('input')
el.value = window.location.href
el.id = 'url-input'
document.body.appendChild(el)
el.select()
document.execCommand('copy')
el.remove()
}
function newParty() {
// Push the root URL
router.push('/')
// Clean state
const resetState = clonedeep(initialAppState)
Object.keys(resetState).forEach((key) => {
appState[key] = resetState[key]
})
// Set party to be editable
appState.party.editable = true
}
function logout() {
deleteCookie('account')
deleteCookie('user')
// Clean state
const resetState = clonedeep(initialAccountState)
Object.keys(resetState).forEach((key) => {
if (key !== 'language') accountState[key] = resetState[key]
})
if (router.route != '/new') appState.party.editable = false
router.push('/')
return false
}
function toggleFavorite() {
if (party.favorited) unsaveFavorite()
else saveFavorite()
}
function saveFavorite() {
if (party.id)
api.saveTeam({ id: party.id, params: headers }).then((response) => {
if (response.status == 201) appState.party.favorited = true
})
else console.error('Failed to save team: No party ID')
}
function unsaveFavorite() {
if (party.id)
api.unsaveTeam({ id: party.id, params: headers }).then((response) => {
if (response.status == 200) appState.party.favorited = false
})
else console.error('Failed to unsave team: No party ID')
}
const copyButton = () => {
if (router.route === '/p/[party]')
return (
<Button
accessoryIcon={<LinkIcon className="stroke" />}
blended={true}
text={t('buttons.copy')}
onClick={copyToClipboard}
/>
)
}
const leftNav = () => {
return (
<div className="dropdown">
<Button
accessoryIcon={<MenuIcon />}
blended={true}
text={t('buttons.menu')}
/>
{account.user ? (
<HeaderMenu
authenticated={account.authorized}
username={account.user.username}
logout={logout}
/>
) : (
<HeaderMenu authenticated={account.authorized} />
)}
</div>
)
}
const saveButton = () => {
if (party.favorited)
return (
<Button
accessoryIcon={<SaveIcon />}
blended={true}
text="Saved"
onClick={toggleFavorite}
/>
)
else
return (
<Button
accessoryIcon={<SaveIcon />}
blended={true}
text="Save"
onClick={toggleFavorite}
/>
)
}
const rightNav = () => {
return (
<div>
{router.route === '/p/[party]' &&
account.user &&
(!party.user || party.user.id !== account.user.id)
? saveButton()
: ''}
{copyButton()}
<Button
accessoryIcon={<AddIcon className="Add" />}
blended={true}
text={t('buttons.new')}
onClick={newParty}
/>
</div>
)
}
return <Header position="top" left={leftNav()} right={rightNav()} />
}
export default TopHeader

View file

@ -53,11 +53,8 @@
background-image: url('/icons/uncap/purple-hover@3x.png'); background-image: url('/icons/uncap/purple-hover@3x.png');
} }
} }
}
// Phone up to iPhone 14 Pro Max @include breakpoint(phone) {
@media only screen and (max-width: 430px) and (max-height: 850px) and (-webkit-device-pixel-ratio: 3) {
.UncapStar {
background-size: cover; background-size: cover;
height: 14px; height: 14px;
width: 14px; width: 14px;

View file

@ -2,7 +2,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
@media (max-width: $phone) { @include breakpoint(phone) {
display: grid; display: grid;
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;
} }
@ -23,12 +23,12 @@
margin-bottom: $unit-3x; margin-bottom: $unit-3x;
margin-right: $unit-3x; margin-right: $unit-3x;
@media (max-width: $tablet) { @include breakpoint(tablet) {
margin-bottom: $unit-2x; margin-bottom: $unit-2x;
margin-right: $unit-2x; margin-right: $unit-2x;
} }
@media (max-width: $phone) { @include breakpoint(tablet) {
margin-bottom: $unit; margin-bottom: $unit;
margin-right: $unit; margin-right: $unit;
} }

View file

@ -5,7 +5,7 @@
min-height: 139px; min-height: 139px;
position: relative; position: relative;
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
min-height: auto; min-height: auto;
} }
@ -28,11 +28,12 @@
margin-right: $unit-3x; margin-right: $unit-3x;
max-width: 200px; max-width: 200px;
@media (max-width: $tablet) { @include breakpoint(tablet) {
margin-right: $unit-2x; margin-right: $unit-2x;
} }
@media (max-width: $phone) { @include breakpoint(phone) {
margin-right: $unit-2x;
margin-right: $unit; margin-right: $unit;
} }
@ -62,7 +63,7 @@
} }
} }
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
width: 25vw; width: 25vw;
} }
} }
@ -94,7 +95,7 @@
} }
} }
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
width: 20vw; width: 20vw;
} }
} }

View file

@ -113,7 +113,7 @@ select {
margin-top: $unit * 3; margin-top: $unit * 3;
min-width: 752px; min-width: 752px;
@media (max-width: $medium-screen) { @include breakpoint(tablet) {
min-width: auto; min-width: auto;
width: 100%; width: 100%;
} }
@ -291,7 +291,7 @@ i.tag {
gap: $unit; gap: $unit;
padding: 0 ($unit * 3); padding: 0 ($unit * 3);
@media (max-width: $phone) { @include breakpoint(phone) {
display: grid; display: grid;
gap: 8px; gap: 8px;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;

28
styles/mixins.scss Normal file
View file

@ -0,0 +1,28 @@
// use with @include
@mixin breakpoint($breakpoint) {
$phone-width: 430px;
$phone-height: 850px;
$tablet-width: 1024px;
$tablet-height: 1024px;
@if $breakpoint == tablet {
// prettier-ignore
@media only screen
and (max-width: $tablet-width)
and (max-height: $tablet-height)
and (-webkit-min-device-pixel-ratio: 2) {
@content;
}
}
@if $breakpoint == phone {
// prettier-ignore
@media only screen
and (max-width: $phone-width)
and (max-height: $phone-height)
and (-webkit-min-device-pixel-ratio: 2) {
@content;
}
}
}

View file

@ -1,4 +1,5 @@
// @import 'include-media/dist/_include-media'; // @import 'include-media/dist/_include-media';
@import 'mixins.scss';
// Breakpoints // Breakpoints
$breakpoints: ( $breakpoints: (

5
types/GranblueCookie.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
interface GranblueCookie {
account: AccountCookie
user: UserCookie
locale: string
}

23
utils/retrieveCookies.tsx Normal file
View file

@ -0,0 +1,23 @@
import { getCookies } from 'cookies-next'
import { NextApiRequest, NextApiResponse } from 'next'
export default function retrieveCookies(
req?: NextApiRequest,
res?: NextApiResponse
): GranblueCookie | undefined {
const cookies = getCookies({ req, res })
if (!cookies) return undefined
const {
account: accountData,
user: userData,
NEXT_LOCALE: localeData,
} = cookies
if (!accountData || !userData) return undefined
const account = JSON.parse(decodeURIComponent(accountData)) ?? undefined
const user = JSON.parse(decodeURIComponent(userData)) ?? undefined
const locale = localeData as string
return { account, user, locale }
}