Merge pull request #74 from jedmund/fix-media-queries
Fix media queries
This commit is contained in:
commit
b705bf7765
32 changed files with 449 additions and 363 deletions
|
|
@ -10,7 +10,8 @@
|
|||
gap: 6px;
|
||||
|
||||
&:hover,
|
||||
&.Blended:hover {
|
||||
&.Blended:hover,
|
||||
&.Blended.Active {
|
||||
background: var(--button-bg-hover);
|
||||
cursor: pointer;
|
||||
color: var(--button-text-hover);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
padding: 0;
|
||||
max-width: 761px;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
& > * {
|
||||
margin-right: $unit * 3;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
margin-right: inherit;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
height: auto;
|
||||
width: 131px;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
width: 17vw;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
min-width: inherit;
|
||||
min-height: inherit;
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
position: relative;
|
||||
left: 9px;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(phone) {
|
||||
left: auto;
|
||||
max-width: auto;
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
position: relative;
|
||||
left: 8px;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
left: auto;
|
||||
max-width: auto;
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
width: 100%;
|
||||
max-width: 996px;
|
||||
|
||||
@media (max-width: $tablet) {
|
||||
@include breakpoint(tablet) {
|
||||
position: static;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
gap: $unit;
|
||||
width: auto;
|
||||
|
||||
@media (max-width: $tablet) {
|
||||
@include breakpoint(tablet) {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
background-color: var(--select-contained-bg-hover);
|
||||
}
|
||||
|
||||
@media (max-width: $tablet) {
|
||||
@include breakpoint(tablet) {
|
||||
width: 100%;
|
||||
max-width: inherit;
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
aspect-ratio: 3/2;
|
||||
border-radius: $card-corner;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: $unit;
|
||||
padding: $unit-2x;
|
||||
max-width: 320px;
|
||||
min-width: 320px;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background: var(--grid-rep-hover);
|
||||
|
|
@ -20,48 +21,58 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Grid .weapon {
|
||||
.Grid .Weapon {
|
||||
box-shadow: inset 0 0 0 1px var(--grid-border-color);
|
||||
}
|
||||
|
||||
@include breakpoint(phone) {
|
||||
background: inherit;
|
||||
|
||||
.Grid .Weapon {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-shrink: 0;
|
||||
gap: $unit;
|
||||
& > .Grid {
|
||||
aspect-ratio: 2/1;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr; /* left column takes up 1 fraction, right column takes up 3 fractions */
|
||||
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);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.grid_mainhand {
|
||||
$d: 64px;
|
||||
aspect-ratio: 200 / 418;
|
||||
height: auto;
|
||||
max-width: $d;
|
||||
min-width: $d;
|
||||
.Mainhand.Weapon {
|
||||
grid-column: 1 / 2; /* spans one column */
|
||||
}
|
||||
|
||||
.grid_weapons {
|
||||
$p: 29.5%;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: fit-content($p) fit-content($p) fit-content($p);
|
||||
grid-template-rows: fit-content($p) fit-content($p) fit-content($p);
|
||||
.GridWeapons {
|
||||
display: grid; /* make the right-images container a grid */
|
||||
grid-template-columns: repeat(
|
||||
3,
|
||||
1fr
|
||||
); /* create 3 columns, each taking up 1 fraction */
|
||||
grid-template-rows: repeat(
|
||||
3,
|
||||
1fr
|
||||
); /* create 3 rows, each taking up 1 fraction */
|
||||
gap: $unit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grid_weapon {
|
||||
.Grid.Weapon {
|
||||
aspect-ratio: 160 / 92;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.grid_mainhand img[src*='jpg'],
|
||||
.grid_weapon img[src*='jpg'] {
|
||||
.Mainhand.Weapon img[src*='jpg'],
|
||||
.Grid.Weapon img[src*='jpg'] {
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
|
|||
|
|
@ -212,15 +212,12 @@ const GridRep = (props: Props) => {
|
|||
<a className="GridRep">
|
||||
{props.displayUser ? detailsWithUsername : details}
|
||||
<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) => {
|
||||
return (
|
||||
<li
|
||||
key={`${props.shortcode}-${i}`}
|
||||
className="weapon grid_weapon"
|
||||
>
|
||||
<li key={`${props.shortcode}-${i}`} className="Grid Weapon">
|
||||
{generateGridImage(i)}
|
||||
</li>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
.GridRepCollection {
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
transition: opacity 0.14s ease-in-out;
|
||||
justify-items: center;
|
||||
// width: fit-content;
|
||||
width: auto;
|
||||
width: 100%;
|
||||
max-width: 996px;
|
||||
|
||||
@media (max-width: $tablet) {
|
||||
grid-template-columns: minmax(320px, 1fr);
|
||||
@include breakpoint(tablet) {
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
max-width: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: inherit;
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
.Header {
|
||||
#Header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: $unit;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
&.bottom {
|
||||
position: sticky;
|
||||
bottom: $unit * 2;
|
||||
}
|
||||
|
||||
#right > div {
|
||||
#Right > div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
gap: $unit;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
#DropdownWrapper {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding-bottom: $unit;
|
||||
|
||||
&:hover .Menu,
|
||||
.Menu.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
padding-right: $unit-4x;
|
||||
|
||||
|
|
@ -25,18 +26,10 @@
|
|||
background: var(--button-bg-hover);
|
||||
color: var(--button-text-hover);
|
||||
}
|
||||
|
||||
.Menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.push {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
.Button .Text {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
interface Props {
|
||||
position: 'top' | 'bottom'
|
||||
left: JSX.Element
|
||||
right: JSX.Element
|
||||
}
|
||||
const Header = () => {
|
||||
// Localization
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// 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 (
|
||||
<nav className={`Header ${props.position}`}>
|
||||
<div id="left">{props.left}</div>
|
||||
<div className="push" />
|
||||
<div id="right">{props.right}</div>
|
||||
<nav id="Header">
|
||||
<div id="Left">{leftNav()}</div>
|
||||
<div id="Right">{rightNav()}</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
.Menu {
|
||||
background: var(--menu-bg);
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
min-width: 220px;
|
||||
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
|
||||
z-index: 10;
|
||||
|
||||
@include breakpoint(phone) {
|
||||
left: $unit-2x;
|
||||
right: $unit-2x;
|
||||
}
|
||||
}
|
||||
|
||||
.MenuItem {
|
||||
|
|
@ -29,6 +35,16 @@
|
|||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(phone) {
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
cursor: default;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.profile > div {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { getCookie, setCookie } from 'cookies-next'
|
||||
import { useRouter } from 'next/router'
|
||||
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 * as Switch from '@radix-ui/react-switch'
|
||||
|
|
@ -14,36 +16,52 @@ import LoginModal from '~components/LoginModal'
|
|||
import SignupModal from '~components/SignupModal'
|
||||
|
||||
import './index.scss'
|
||||
import { accountState } from '~utils/accountState'
|
||||
|
||||
interface Props {
|
||||
authenticated: boolean
|
||||
open: boolean
|
||||
username?: string
|
||||
onClickOutside: () => void
|
||||
logout?: () => void
|
||||
}
|
||||
|
||||
const HeaderMenu = (props: Props) => {
|
||||
// Setup
|
||||
const router = useRouter()
|
||||
const data: GranblueCookie | undefined = retrieveCookies()
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
const accountCookie = getCookie('account')
|
||||
const accountData: AccountCookie = accountCookie
|
||||
? JSON.parse(accountCookie as string)
|
||||
: null
|
||||
// Refs
|
||||
const ref: React.RefObject<HTMLDivElement> = React.createRef()
|
||||
|
||||
const userCookie = getCookie('user')
|
||||
const userData: UserCookie = userCookie
|
||||
? JSON.parse(userCookie as string)
|
||||
: null
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: Event) => {
|
||||
const target = event.target instanceof Element ? event.target : 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)
|
||||
|
||||
useEffect(() => {
|
||||
const locale = localeCookie
|
||||
const locale = data?.locale
|
||||
setChecked(locale === 'ja' ? true : false)
|
||||
}, [localeCookie])
|
||||
}, [data?.locale])
|
||||
|
||||
function handleCheckedChange(value: boolean) {
|
||||
const language = value ? 'ja' : 'en'
|
||||
|
|
@ -51,66 +69,70 @@ const HeaderMenu = (props: Props) => {
|
|||
router.push(router.asPath, undefined, { locale: language })
|
||||
}
|
||||
|
||||
const menuClasses = classNames({
|
||||
Menu: true,
|
||||
auth: props.authenticated,
|
||||
open: props.open,
|
||||
})
|
||||
|
||||
function authItems() {
|
||||
return (
|
||||
<nav>
|
||||
<ul className="Menu auth">
|
||||
<div className="MenuGroup">
|
||||
<li className="MenuItem profile">
|
||||
<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">
|
||||
<ul className={menuClasses}>
|
||||
<div className="MenuGroup">
|
||||
<li className="MenuItem profile">
|
||||
<Link href={`/${data?.account.username}` || ''} passHref>
|
||||
<div>
|
||||
<span>{t('menu.guides')}</span>
|
||||
<i className="tag">{t('coming_soon')}</i>
|
||||
<span>{data?.account.username}</span>
|
||||
<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>
|
||||
</li>
|
||||
</div>
|
||||
<div className="MenuGroup">
|
||||
<AboutModal />
|
||||
<ChangelogModal />
|
||||
<RoadmapModal />
|
||||
</div>
|
||||
<div className="MenuGroup">
|
||||
<AccountModal
|
||||
username={accountState.account.user?.username}
|
||||
picture={accountState.account.user?.picture}
|
||||
gender={accountState.account.user?.gender}
|
||||
language={accountState.account.user?.language}
|
||||
theme={accountState.account.user?.theme}
|
||||
/>
|
||||
<li className="MenuItem" onClick={props.logout}>
|
||||
<span>{t('menu.logout')}</span>
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
</nav>
|
||||
</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>
|
||||
<span>{t('menu.guides')}</span>
|
||||
<i className="tag">{t('coming_soon')}</i>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
<div className="MenuGroup">
|
||||
<AboutModal />
|
||||
<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() {
|
||||
return (
|
||||
<ul className="Menu unauth">
|
||||
<ul className={menuClasses}>
|
||||
<div className="MenuGroup">
|
||||
<li className="MenuItem language">
|
||||
<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
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
display: flex;
|
||||
margin-bottom: $unit * 3;
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
flex-direction: column;
|
||||
gap: $unit;
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
width: $width;
|
||||
transition: box-shadow 0.15s ease-in-out;
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
aspect-ratio: 16/9;
|
||||
margin: 0;
|
||||
width: inherit;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ReactElement } from 'react'
|
||||
import TopHeader from '~components/TopHeader'
|
||||
import TopHeader from '~components/Header'
|
||||
|
||||
interface Props {
|
||||
children: ReactElement
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
flex-direction: column;
|
||||
margin-top: $unit-4x;
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
padding: 0 $unit;
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
width: 100%;
|
||||
|
||||
textarea {
|
||||
min-height: $unit * 20;
|
||||
min-height: $unit * 22;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
width: 60%;
|
||||
height: 60%;
|
||||
|
||||
@media (max-width: $tablet) {
|
||||
@include breakpoint(tablet) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
max-width: 760px;
|
||||
position: relative;
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
gap: $unit;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
|
|
@ -20,16 +20,16 @@
|
|||
&.Editable {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.SegmentedControl {
|
||||
.SegmentedControl {
|
||||
flex-grow: 1;
|
||||
|
||||
@include breakpoint(phone) {
|
||||
flex-grow: 1;
|
||||
|
||||
@media (max-width: $phone) {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
}
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
position: absolute;
|
||||
right: 0px;
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
position: static;
|
||||
|
||||
.Text {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
gap: 0;
|
||||
padding: 0;
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
min-width: inherit;
|
||||
min-height: inherit;
|
||||
width: 96%; // is this even right
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
padding: 0 ($unit * 1.5);
|
||||
overflow-y: scroll;
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
min-width: initial;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
&.Friend {
|
||||
max-width: 78px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
width: 182px;
|
||||
height: auto;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
width: 20.3vw;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
// min-height: 141px;
|
||||
min-height: 180px;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
min-height: 16.5vw;
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
width: 148px;
|
||||
height: auto;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
width: 20vw;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -53,11 +53,8 @@
|
|||
background-image: url('/icons/uncap/purple-hover@3x.png');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phone up to iPhone 14 Pro Max
|
||||
@media only screen and (max-width: 430px) and (max-height: 850px) and (-webkit-device-pixel-ratio: 3) {
|
||||
.UncapStar {
|
||||
@include breakpoint(phone) {
|
||||
background-size: cover;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
|
@ -23,12 +23,12 @@
|
|||
margin-bottom: $unit-3x;
|
||||
margin-right: $unit-3x;
|
||||
|
||||
@media (max-width: $tablet) {
|
||||
@include breakpoint(tablet) {
|
||||
margin-bottom: $unit-2x;
|
||||
margin-right: $unit-2x;
|
||||
}
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(tablet) {
|
||||
margin-bottom: $unit;
|
||||
margin-right: $unit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
min-height: 139px;
|
||||
position: relative;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
|
|
@ -28,11 +28,12 @@
|
|||
margin-right: $unit-3x;
|
||||
max-width: 200px;
|
||||
|
||||
@media (max-width: $tablet) {
|
||||
@include breakpoint(tablet) {
|
||||
margin-right: $unit-2x;
|
||||
}
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
margin-right: $unit-2x;
|
||||
margin-right: $unit;
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +63,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
width: 25vw;
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +95,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
width: 20vw;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ select {
|
|||
margin-top: $unit * 3;
|
||||
min-width: 752px;
|
||||
|
||||
@media (max-width: $medium-screen) {
|
||||
@include breakpoint(tablet) {
|
||||
min-width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -291,7 +291,7 @@ i.tag {
|
|||
gap: $unit;
|
||||
padding: 0 ($unit * 3);
|
||||
|
||||
@media (max-width: $phone) {
|
||||
@include breakpoint(phone) {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
|
|
|||
28
styles/mixins.scss
Normal file
28
styles/mixins.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
// @import 'include-media/dist/_include-media';
|
||||
@import 'mixins.scss';
|
||||
|
||||
// Breakpoints
|
||||
$breakpoints: (
|
||||
|
|
|
|||
5
types/GranblueCookie.d.ts
vendored
Normal file
5
types/GranblueCookie.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
interface GranblueCookie {
|
||||
account: AccountCookie
|
||||
user: UserCookie
|
||||
locale: string
|
||||
}
|
||||
23
utils/retrieveCookies.tsx
Normal file
23
utils/retrieveCookies.tsx
Normal 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 }
|
||||
}
|
||||
Loading…
Reference in a new issue