commit
3f24886b98
42 changed files with 677 additions and 361 deletions
|
|
@ -80,6 +80,10 @@ const AccountModal = (props: Props) => {
|
||||||
// const [privateProfile, setPrivateProfile] = useState(false)
|
// const [privateProfile, setPrivateProfile] = useState(false)
|
||||||
|
|
||||||
// Setup
|
// Setup
|
||||||
|
const [pictureOpen, setPictureOpen] = useState(false)
|
||||||
|
const [genderOpen, setGenderOpen] = useState(false)
|
||||||
|
const [languageOpen, setLanguageOpen] = useState(false)
|
||||||
|
const [themeOpen, setThemeOpen] = useState(false)
|
||||||
|
|
||||||
// UI management
|
// UI management
|
||||||
function openChange(open: boolean) {
|
function openChange(open: boolean) {
|
||||||
|
|
@ -87,16 +91,10 @@ const AccountModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSelect(name: 'picture' | 'gender' | 'language' | 'theme') {
|
function openSelect(name: 'picture' | 'gender' | 'language' | 'theme') {
|
||||||
const stateVars = selectOpenState
|
setPictureOpen(name === 'picture' ? !pictureOpen : false)
|
||||||
Object.keys(stateVars).forEach((key) => {
|
setGenderOpen(name === 'gender' ? !genderOpen : false)
|
||||||
if (key === name) {
|
setLanguageOpen(name === 'language' ? !languageOpen : false)
|
||||||
stateVars[name] = true
|
setThemeOpen(name === 'theme' ? !themeOpen : false)
|
||||||
} else {
|
|
||||||
stateVars[key] = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setSelectOpenState(stateVars)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
|
|
@ -117,6 +115,14 @@ const AccountModal = (props: Props) => {
|
||||||
setAppTheme(value)
|
setAppTheme(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||||
|
if (pictureOpen || genderOpen || languageOpen || themeOpen) {
|
||||||
|
return event.preventDefault()
|
||||||
|
} else {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// API calls
|
// API calls
|
||||||
function update(event: React.FormEvent<HTMLFormElement>) {
|
function update(event: React.FormEvent<HTMLFormElement>) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
@ -189,9 +195,10 @@ const AccountModal = (props: Props) => {
|
||||||
description={t('modals.settings.descriptions.picture')}
|
description={t('modals.settings.descriptions.picture')}
|
||||||
className="Image"
|
className="Image"
|
||||||
label={t('modals.settings.labels.picture')}
|
label={t('modals.settings.labels.picture')}
|
||||||
open={selectOpenState.picture}
|
open={pictureOpen}
|
||||||
onClick={() => openSelect('picture')}
|
onOpenChange={() => openSelect('picture')}
|
||||||
onChange={handlePictureChange}
|
onChange={handlePictureChange}
|
||||||
|
onClose={() => setPictureOpen(false)}
|
||||||
imageAlt={t('modals.settings.labels.image_alt')}
|
imageAlt={t('modals.settings.labels.image_alt')}
|
||||||
imageClass={pictureData.find((i) => i.filename === picture)?.element}
|
imageClass={pictureData.find((i) => i.filename === picture)?.element}
|
||||||
imageSrc={[`/profile/${picture}.png`, `/profile/${picture}@2x.png 2x`]}
|
imageSrc={[`/profile/${picture}.png`, `/profile/${picture}@2x.png 2x`]}
|
||||||
|
|
@ -206,9 +213,10 @@ const AccountModal = (props: Props) => {
|
||||||
name="gender"
|
name="gender"
|
||||||
description={t('modals.settings.descriptions.gender')}
|
description={t('modals.settings.descriptions.gender')}
|
||||||
label={t('modals.settings.labels.gender')}
|
label={t('modals.settings.labels.gender')}
|
||||||
open={selectOpenState.gender}
|
open={genderOpen}
|
||||||
onClick={() => openSelect('gender')}
|
onOpenChange={() => openSelect('gender')}
|
||||||
onChange={handleGenderChange}
|
onChange={handleGenderChange}
|
||||||
|
onClose={() => setGenderOpen(false)}
|
||||||
value={`${gender}`}
|
value={`${gender}`}
|
||||||
>
|
>
|
||||||
<SelectItem key="gran" value="0">
|
<SelectItem key="gran" value="0">
|
||||||
|
|
@ -224,9 +232,10 @@ const AccountModal = (props: Props) => {
|
||||||
<SelectTableField
|
<SelectTableField
|
||||||
name="language"
|
name="language"
|
||||||
label={t('modals.settings.labels.language')}
|
label={t('modals.settings.labels.language')}
|
||||||
open={selectOpenState.language}
|
open={languageOpen}
|
||||||
onClick={() => openSelect('language')}
|
onOpenChange={() => openSelect('language')}
|
||||||
onChange={handleLanguageChange}
|
onChange={handleLanguageChange}
|
||||||
|
onClose={() => setLanguageOpen(false)}
|
||||||
value={language}
|
value={language}
|
||||||
>
|
>
|
||||||
<SelectItem key="en" value="en">
|
<SelectItem key="en" value="en">
|
||||||
|
|
@ -242,9 +251,10 @@ const AccountModal = (props: Props) => {
|
||||||
<SelectTableField
|
<SelectTableField
|
||||||
name="theme"
|
name="theme"
|
||||||
label={t('modals.settings.labels.theme')}
|
label={t('modals.settings.labels.theme')}
|
||||||
open={selectOpenState.theme}
|
open={themeOpen}
|
||||||
onClick={() => openSelect('theme')}
|
onOpenChange={() => openSelect('theme')}
|
||||||
onChange={handleThemeChange}
|
onChange={handleThemeChange}
|
||||||
|
onClose={() => setThemeOpen(false)}
|
||||||
value={theme}
|
value={theme}
|
||||||
>
|
>
|
||||||
<SelectItem key="system" value="system">
|
<SelectItem key="system" value="system">
|
||||||
|
|
@ -274,7 +284,11 @@ const AccountModal = (props: Props) => {
|
||||||
<span>{t('menu.settings')}</span>
|
<span>{t('menu.settings')}</span>
|
||||||
</li>
|
</li>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="Account Dialog">
|
<DialogContent
|
||||||
|
className="Account Dialog"
|
||||||
|
onOpenAutoFocus={(event: Event) => {}}
|
||||||
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
|
>
|
||||||
<div className="DialogHeader">
|
<div className="DialogHeader">
|
||||||
<div className="DialogTop">
|
<div className="DialogTop">
|
||||||
<DialogTitle className="SubTitle">
|
<DialogTitle className="SubTitle">
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ interface Props {
|
||||||
object: 'character' | 'weapon'
|
object: 'character' | 'weapon'
|
||||||
awakeningType?: number
|
awakeningType?: number
|
||||||
awakeningLevel?: number
|
awakeningLevel?: number
|
||||||
|
onOpenChange: (open: boolean) => void
|
||||||
sendValidity: (isValid: boolean) => void
|
sendValidity: (isValid: boolean) => void
|
||||||
sendValues: (type: number, level: number) => void
|
sendValues: (type: number, level: number) => void
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +82,11 @@ const AwakeningSelect = (props: Props) => {
|
||||||
// Classes
|
// Classes
|
||||||
function changeOpen() {
|
function changeOpen() {
|
||||||
setOpen(!open)
|
setOpen(!open)
|
||||||
|
props.onOpenChange(!open)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
props.onOpenChange(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateOptions(object: 'character' | 'weapon') {
|
function generateOptions(object: 'character' | 'weapon') {
|
||||||
|
|
@ -165,7 +171,8 @@ const AwakeningSelect = (props: Props) => {
|
||||||
value={`${awakeningType}`}
|
value={`${awakeningType}`}
|
||||||
open={open}
|
open={open}
|
||||||
onValueChange={handleSelectChange}
|
onValueChange={handleSelectChange}
|
||||||
onClick={() => changeOpen()}
|
onOpenChange={() => changeOpen()}
|
||||||
|
onClose={onClose}
|
||||||
triggerClass="modal"
|
triggerClass="modal"
|
||||||
>
|
>
|
||||||
{generateOptions(props.object)}
|
{generateOptions(props.object)}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ interface ErrorMap {
|
||||||
interface Props {
|
interface Props {
|
||||||
axType: number
|
axType: number
|
||||||
currentSkills?: SimpleAxSkill[]
|
currentSkills?: SimpleAxSkill[]
|
||||||
|
onOpenChange: (index: 1 | 2, open: boolean) => void
|
||||||
sendValidity: (isValid: boolean) => void
|
sendValidity: (isValid: boolean) => void
|
||||||
sendValues: (
|
sendValues: (
|
||||||
primaryAxModifier: number,
|
primaryAxModifier: number,
|
||||||
|
|
@ -193,9 +194,22 @@ const AXSelect = (props: Props) => {
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSelect(ref: ForwardedRef<HTMLButtonElement>) {
|
function openSelect(index: 1 | 2) {
|
||||||
if (ref === primaryAxModifierSelect) setOpenAX1(!openAX1)
|
if (index === 1) {
|
||||||
if (ref === secondaryAxModifierSelect) setOpenAX2(!openAX2)
|
setOpenAX1(!openAX1)
|
||||||
|
setOpenAX2(false)
|
||||||
|
props.onOpenChange(1, !openAX1)
|
||||||
|
props.onOpenChange(2, false)
|
||||||
|
} else if (index === 2) {
|
||||||
|
setOpenAX2(!openAX2)
|
||||||
|
setOpenAX1(false)
|
||||||
|
props.onOpenChange(2, !openAX2)
|
||||||
|
props.onOpenChange(1, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose(index: 1 | 2) {
|
||||||
|
props.onOpenChange(index, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateOptions(modifierSet: number) {
|
function generateOptions(modifierSet: number) {
|
||||||
|
|
@ -392,8 +406,9 @@ const AXSelect = (props: Props) => {
|
||||||
key="ax1"
|
key="ax1"
|
||||||
value={`${primaryAxModifier}`}
|
value={`${primaryAxModifier}`}
|
||||||
open={openAX1}
|
open={openAX1}
|
||||||
|
onClose={() => onClose(1)}
|
||||||
|
onOpenChange={() => openSelect(1)}
|
||||||
onValueChange={handleAX1SelectChange}
|
onValueChange={handleAX1SelectChange}
|
||||||
onClick={() => openSelect(primaryAxModifierSelect)}
|
|
||||||
triggerClass="modal"
|
triggerClass="modal"
|
||||||
>
|
>
|
||||||
{generateOptions(0)}
|
{generateOptions(0)}
|
||||||
|
|
@ -419,8 +434,9 @@ const AXSelect = (props: Props) => {
|
||||||
key="ax2"
|
key="ax2"
|
||||||
value={`${secondaryAxModifier}`}
|
value={`${secondaryAxModifier}`}
|
||||||
open={openAX2}
|
open={openAX2}
|
||||||
|
onClose={() => onClose(2)}
|
||||||
|
onOpenChange={() => openSelect(2)}
|
||||||
onValueChange={handleAX2SelectChange}
|
onValueChange={handleAX2SelectChange}
|
||||||
onClick={() => openSelect(secondaryAxModifierSelect)}
|
|
||||||
triggerClass="modal"
|
triggerClass="modal"
|
||||||
ref={secondaryAxModifierSelect}
|
ref={secondaryAxModifierSelect}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@
|
||||||
// box-shadow: 0 2px rgba(255, 255, 255, 1);
|
// box-shadow: 0 2px rgba(255, 255, 255, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Input {
|
||||||
|
padding: $unit * 1.5 $unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
.Counter {
|
.Counter {
|
||||||
color: $grey-55;
|
color: $grey-55;
|
||||||
font-weight: $bold;
|
font-weight: $bold;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ interface Props
|
||||||
extends React.DetailedHTMLProps<
|
extends React.DetailedHTMLProps<
|
||||||
React.DialogHTMLAttributes<HTMLDivElement>,
|
React.DialogHTMLAttributes<HTMLDivElement>,
|
||||||
HTMLDivElement
|
HTMLDivElement
|
||||||
> {}
|
> {
|
||||||
|
onEscapeKeyDown: (event: KeyboardEvent) => void
|
||||||
|
onOpenAutoFocus: (event: Event) => void
|
||||||
|
}
|
||||||
|
|
||||||
export const DialogContent = React.forwardRef<HTMLDivElement, Props>(
|
export const DialogContent = React.forwardRef<HTMLDivElement, Props>(
|
||||||
function dialog({ children, ...props }, forwardedRef) {
|
function dialog({ children, ...props }, forwardedRef) {
|
||||||
|
|
@ -25,6 +28,8 @@ export const DialogContent = React.forwardRef<HTMLDivElement, Props>(
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
className={classes}
|
className={classes}
|
||||||
{...props}
|
{...props}
|
||||||
|
onOpenAutoFocus={props.onOpenAutoFocus}
|
||||||
|
onEscapeKeyDown={props.onEscapeKeyDown}
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -60,12 +60,18 @@ const FilterBar = (props: Props) => {
|
||||||
props.onFilter({ raidSlug: slug })
|
props.onFilter({ raidSlug: slug })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSelectChange(name: 'element' | 'recency') {
|
||||||
|
setElementOpen(name === 'element' ? !elementOpen : false)
|
||||||
|
setRecencyOpen(name === 'recency' ? !recencyOpen : false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
{props.children}
|
{props.children}
|
||||||
<Select
|
<Select
|
||||||
value={`${props.element}`}
|
value={`${props.element}`}
|
||||||
open={elementOpen}
|
open={elementOpen}
|
||||||
|
onOpenChange={() => onSelectChange('element')}
|
||||||
onValueChange={elementSelectChanged}
|
onValueChange={elementSelectChanged}
|
||||||
onClick={openElementSelect}
|
onClick={openElementSelect}
|
||||||
>
|
>
|
||||||
|
|
@ -106,6 +112,7 @@ const FilterBar = (props: Props) => {
|
||||||
value={`${props.recency}`}
|
value={`${props.recency}`}
|
||||||
trigger={'All time'}
|
trigger={'All time'}
|
||||||
open={recencyOpen}
|
open={recencyOpen}
|
||||||
|
onOpenChange={() => onSelectChange('recency')}
|
||||||
onValueChange={recencySelectChanged}
|
onValueChange={recencySelectChanged}
|
||||||
onClick={openRecencySelect}
|
onClick={openRecencySelect}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,10 @@
|
||||||
.bottom {
|
.bottom {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
|
a.user:hover {
|
||||||
|
color: var(--link-text-hover);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.raid,
|
.raid,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
@ -132,11 +133,28 @@ const GridRep = (props: Props) => {
|
||||||
} else return <div className="no-user" />
|
} else return <div className="no-user" />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const linkedAttribution = () => (
|
||||||
|
<Link href={`/${props.user ? props.user.username : '#'}`}>
|
||||||
|
<a
|
||||||
|
className={userClass}
|
||||||
|
href={`/${props.user ? props.user.username : '#'}`}
|
||||||
|
>
|
||||||
|
{userImage()}
|
||||||
|
{props.user ? props.user.username : t('no_user')}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
|
||||||
|
const unlinkedAttribution = () => (
|
||||||
|
<div className={userClass}>
|
||||||
|
{userImage()}
|
||||||
|
{props.user ? props.user.username : t('no_user')}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const details = (
|
const details = (
|
||||||
<div className="Details">
|
<div className="Details">
|
||||||
<h2 className={titleClass} onClick={navigate}>
|
<h2 className={titleClass}>{props.name ? props.name : t('no_title')}</h2>
|
||||||
{props.name ? props.name : t('no_title')}
|
|
||||||
</h2>
|
|
||||||
<div className="bottom">
|
<div className="bottom">
|
||||||
<div className={raidClass}>
|
<div className={raidClass}>
|
||||||
{props.raid ? props.raid.name[locale] : t('no_raid')}
|
{props.raid ? props.raid.name[locale] : t('no_raid')}
|
||||||
|
|
@ -152,7 +170,7 @@ const GridRep = (props: Props) => {
|
||||||
<div className="Details">
|
<div className="Details">
|
||||||
<div className="top">
|
<div className="top">
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<h2 className={titleClass} onClick={navigate}>
|
<h2 className={titleClass}>
|
||||||
{props.name ? props.name : t('no_title')}
|
{props.name ? props.name : t('no_title')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className={raidClass}>
|
<div className={raidClass}>
|
||||||
|
|
@ -162,23 +180,25 @@ const GridRep = (props: Props) => {
|
||||||
{account.authorized &&
|
{account.authorized &&
|
||||||
((props.user && account.user && account.user.id !== props.user.id) ||
|
((props.user && account.user && account.user.id !== props.user.id) ||
|
||||||
!props.user) ? (
|
!props.user) ? (
|
||||||
<Button
|
<Link href="#">
|
||||||
className="Save"
|
<a href="#">
|
||||||
accessoryIcon={<SaveIcon className="stroke" />}
|
<Button
|
||||||
active={props.favorited}
|
className="Save"
|
||||||
contained={true}
|
accessoryIcon={<SaveIcon className="stroke" />}
|
||||||
buttonSize="small"
|
active={props.favorited}
|
||||||
onClick={sendSaveData}
|
contained={true}
|
||||||
/>
|
buttonSize="small"
|
||||||
|
onClick={sendSaveData}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="bottom">
|
<div className="bottom">
|
||||||
<div className={userClass}>
|
{props.user ? linkedAttribution() : unlinkedAttribution()}
|
||||||
{userImage()}
|
|
||||||
{props.user ? props.user.username : t('no_user')}
|
|
||||||
</div>
|
|
||||||
<time className="last-updated" dateTime={props.createdAt.toISOString()}>
|
<time className="last-updated" dateTime={props.createdAt.toISOString()}>
|
||||||
{formatTimeAgo(props.createdAt, locale)}
|
{formatTimeAgo(props.createdAt, locale)}
|
||||||
</time>
|
</time>
|
||||||
|
|
@ -187,25 +207,27 @@ const GridRep = (props: Props) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="GridRep">
|
<Link href={`/p/${props.shortcode}`}>
|
||||||
{props.displayUser ? detailsWithUsername : details}
|
<a className="GridRep">
|
||||||
<div className="Grid" onClick={navigate}>
|
{props.displayUser ? detailsWithUsername : details}
|
||||||
<div className="weapon grid_mainhand">{generateMainhandImage()}</div>
|
<div className="Grid">
|
||||||
|
<div className="weapon grid_mainhand">{generateMainhandImage()}</div>
|
||||||
|
|
||||||
<ul className="grid_weapons">
|
<ul className="grid_weapons">
|
||||||
{Array.from(Array(numWeapons)).map((x, i) => {
|
{Array.from(Array(numWeapons)).map((x, i) => {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={`${props.shortcode}-${i}`}
|
key={`${props.shortcode}-${i}`}
|
||||||
className="weapon grid_weapon"
|
className="weapon grid_weapon"
|
||||||
>
|
>
|
||||||
{generateGridImage(i)}
|
{generateGridImage(i)}
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
.GridRepCollection {
|
.GridRepCollection {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto auto;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
.Header {
|
.Header {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: $unit-2x;
|
margin-bottom: $unit;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&.bottom {
|
&.bottom {
|
||||||
|
|
@ -16,9 +16,10 @@
|
||||||
.dropdown {
|
.dropdown {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
padding-bottom: $unit;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
padding-right: 50px;
|
padding-right: $unit-4x;
|
||||||
|
|
||||||
.Button {
|
.Button {
|
||||||
background: var(--button-bg-hover);
|
background: var(--button-bg-hover);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import Select from '~components/Select'
|
import Select from '~components/Select'
|
||||||
import SelectItem from '~components/SelectItem'
|
import SelectItem from '~components/SelectItem'
|
||||||
|
|
@ -26,6 +27,9 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = router.locale || 'en'
|
const locale = router.locale || 'en'
|
||||||
|
|
||||||
|
// Set up translation
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
// Create snapshot of app state
|
// Create snapshot of app state
|
||||||
const { party } = useSnapshot(appState)
|
const { party } = useSnapshot(appState)
|
||||||
|
|
||||||
|
|
@ -106,11 +110,12 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||||
placeholder={'Select a class...'}
|
placeholder={'Select a class...'}
|
||||||
open={open}
|
open={open}
|
||||||
onClick={openJobSelect}
|
onClick={openJobSelect}
|
||||||
|
onOpenChange={() => setOpen(!open)}
|
||||||
onValueChange={handleChange}
|
onValueChange={handleChange}
|
||||||
triggerClass="Job"
|
triggerClass="Job"
|
||||||
>
|
>
|
||||||
<SelectItem key={-1} value="no-job">
|
<SelectItem key={-1} value="no-job">
|
||||||
No class
|
{t('no_job')}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
{sortedJobs
|
{sortedJobs
|
||||||
? Object.keys(sortedJobs)
|
? Object.keys(sortedJobs)
|
||||||
|
|
|
||||||
|
|
@ -3,29 +3,4 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit;
|
||||||
|
|
||||||
.Button {
|
|
||||||
font-size: $font-regular;
|
|
||||||
padding: ($unit * 1.5) ($unit * 2);
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&.btn-disabled {
|
|
||||||
background: $grey-90;
|
|
||||||
color: $grey-70;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.btn-disabled) {
|
|
||||||
background: $grey-90;
|
|
||||||
color: $grey-50;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $grey-80;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
background: $grey-90;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,15 @@ const LoginModal = (props: Props) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOpenAutoFocus(event: Event) {
|
||||||
|
event.preventDefault()
|
||||||
|
if (emailInput.current) emailInput.current.focus()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={openChange}>
|
<Dialog open={open} onOpenChange={openChange}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
|
|
@ -178,7 +187,11 @@ const LoginModal = (props: Props) => {
|
||||||
<span>{t('menu.login')}</span>
|
<span>{t('menu.login')}</span>
|
||||||
</li>
|
</li>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="Login Dialog">
|
<DialogContent
|
||||||
|
className="Login Dialog"
|
||||||
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
|
onOpenAutoFocus={onOpenAutoFocus}
|
||||||
|
>
|
||||||
<div className="DialogHeader">
|
<div className="DialogHeader">
|
||||||
<div className="DialogTitle">
|
<div className="DialogTitle">
|
||||||
<h1>{t('modals.login.title')}</h1>
|
<h1>{t('modals.login.title')}</h1>
|
||||||
|
|
@ -206,7 +219,10 @@ const LoginModal = (props: Props) => {
|
||||||
ref={passwordInput}
|
ref={passwordInput}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button text={t('modals.login.buttons.confirm')} />
|
<Button
|
||||||
|
disabled={!formValid}
|
||||||
|
text={t('modals.login.buttons.confirm')}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,8 @@ const Party = (props: Props) => {
|
||||||
{navigation}
|
{navigation}
|
||||||
<section id="Party">{currentGrid()}</section>
|
<section id="Party">{currentGrid()}</section>
|
||||||
<PartyDetails
|
<PartyDetails
|
||||||
|
party={props.team}
|
||||||
|
new={props.new || false}
|
||||||
editable={party.editable}
|
editable={party.editable}
|
||||||
updateCallback={updateDetails}
|
updateCallback={updateDetails}
|
||||||
deleteCallback={deleteTeam}
|
deleteCallback={deleteTeam}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
@ -13,18 +14,20 @@ import CharLimitedFieldset from '~components/CharLimitedFieldset'
|
||||||
import RaidDropdown from '~components/RaidDropdown'
|
import RaidDropdown from '~components/RaidDropdown'
|
||||||
import TextFieldset from '~components/TextFieldset'
|
import TextFieldset from '~components/TextFieldset'
|
||||||
|
|
||||||
|
import { accountState } from '~utils/accountState'
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from '~utils/appState'
|
||||||
|
import { formatTimeAgo } from '~utils/timeAgo'
|
||||||
|
|
||||||
import CheckIcon from '~public/icons/Check.svg'
|
import CheckIcon from '~public/icons/Check.svg'
|
||||||
import CrossIcon from '~public/icons/Cross.svg'
|
import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import EditIcon from '~public/icons/Edit.svg'
|
import EditIcon from '~public/icons/Edit.svg'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import Link from 'next/link'
|
|
||||||
import { formatTimeAgo } from '~utils/timeAgo'
|
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
party?: Party
|
||||||
|
new: boolean
|
||||||
editable: boolean
|
editable: boolean
|
||||||
updateCallback: (name?: string, description?: string, raid?: Raid) => void
|
updateCallback: (name?: string, description?: string, raid?: Raid) => void
|
||||||
deleteCallback: (
|
deleteCallback: (
|
||||||
|
|
@ -42,23 +45,24 @@ const PartyDetails = (props: Props) => {
|
||||||
const nameInput = React.createRef<HTMLInputElement>()
|
const nameInput = React.createRef<HTMLInputElement>()
|
||||||
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
const [raidSlug, setRaidSlug] = useState('')
|
const [raidSlug, setRaidSlug] = useState('')
|
||||||
|
|
||||||
const readOnlyClasses = classNames({
|
const readOnlyClasses = classNames({
|
||||||
PartyDetails: true,
|
PartyDetails: true,
|
||||||
ReadOnly: true,
|
ReadOnly: true,
|
||||||
Visible: !party.detailsVisible,
|
Visible: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const editableClasses = classNames({
|
const editableClasses = classNames({
|
||||||
PartyDetails: true,
|
PartyDetails: true,
|
||||||
Editable: true,
|
Editable: true,
|
||||||
Visible: party.detailsVisible,
|
Visible: open,
|
||||||
})
|
})
|
||||||
|
|
||||||
const emptyClasses = classNames({
|
const emptyClasses = classNames({
|
||||||
EmptyDetails: true,
|
EmptyDetails: true,
|
||||||
Visible: !party.detailsVisible,
|
Visible: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const userClass = classNames({
|
const userClass = classNames({
|
||||||
|
|
@ -99,7 +103,7 @@ const PartyDetails = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleDetails() {
|
function toggleDetails() {
|
||||||
appState.party.detailsVisible = !appState.party.detailsVisible
|
setOpen(!open)
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveRaid(slug?: string) {
|
function receiveRaid(slug?: string) {
|
||||||
|
|
@ -115,34 +119,57 @@ const PartyDetails = (props: Props) => {
|
||||||
toggleDetails()
|
toggleDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
const userImage = () => {
|
const userImage = (picture?: string, element?: string) => {
|
||||||
if (party.user)
|
if (picture && element)
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
alt={party.user.avatar.picture}
|
alt={picture}
|
||||||
className={`profile ${party.user.avatar.element}`}
|
className={`profile ${element}`}
|
||||||
srcSet={`/profile/${party.user.avatar.picture}.png,
|
srcSet={`/profile/${picture}.png,
|
||||||
/profile/${party.user.avatar.picture}@2x.png 2x`}
|
/profile/${picture}@2x.png 2x`}
|
||||||
src={`/profile/${party.user.avatar.picture}.png`}
|
src={`/profile/${picture}.png`}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
else return <div className="no-user" />
|
else return <div className="no-user" />
|
||||||
}
|
}
|
||||||
|
|
||||||
const userBlock = () => {
|
const userBlock = (username?: string, picture?: string, element?: string) => {
|
||||||
return (
|
return (
|
||||||
<div className={userClass}>
|
<div className={userClass}>
|
||||||
{userImage()}
|
{userImage(picture, element)}
|
||||||
{party.user ? party.user.username : t('no_user')}
|
{username ? username : t('no_user')}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkedUserBlock = (user: User) => {
|
const renderUserBlock = () => {
|
||||||
|
let username, picture, element
|
||||||
|
if (accountState.account.authorized && props.new) {
|
||||||
|
username = accountState.account.user?.username
|
||||||
|
picture = accountState.account.user?.picture
|
||||||
|
element = accountState.account.user?.element
|
||||||
|
} else if (party.user && !props.new) {
|
||||||
|
username = party.user.username
|
||||||
|
picture = party.user.avatar.picture
|
||||||
|
element = party.user.avatar.element
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username && picture && element) {
|
||||||
|
return linkedUserBlock(username, picture, element)
|
||||||
|
} else if (!props.new) {
|
||||||
|
return userBlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkedUserBlock = (
|
||||||
|
username?: string,
|
||||||
|
picture?: string,
|
||||||
|
element?: string
|
||||||
|
) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Link href={`/${user.username}`} passHref>
|
<Link href={`/${username}`} passHref>
|
||||||
<a className={linkClass}>{userBlock()}</a>
|
<a className={linkClass}>{userBlock(username, picture, element)}</a>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -202,7 +229,7 @@ const PartyDetails = (props: Props) => {
|
||||||
<CharLimitedFieldset
|
<CharLimitedFieldset
|
||||||
fieldName="name"
|
fieldName="name"
|
||||||
placeholder="Name your team"
|
placeholder="Name your team"
|
||||||
value={party.name}
|
value={props.party?.name}
|
||||||
limit={50}
|
limit={50}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
error={errors.name}
|
error={errors.name}
|
||||||
|
|
@ -210,7 +237,7 @@ const PartyDetails = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
<RaidDropdown
|
<RaidDropdown
|
||||||
showAllRaidsOption={false}
|
showAllRaidsOption={false}
|
||||||
currentRaid={party.raid ? party.raid.slug : undefined}
|
currentRaid={props.party?.raid ? props.party?.raid.slug : undefined}
|
||||||
onChange={receiveRaid}
|
onChange={receiveRaid}
|
||||||
/>
|
/>
|
||||||
<TextFieldset
|
<TextFieldset
|
||||||
|
|
@ -218,7 +245,7 @@ const PartyDetails = (props: Props) => {
|
||||||
placeholder={
|
placeholder={
|
||||||
'Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediel’s 1 first\nGood luck with RNG!'
|
'Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediel’s 1 first\nGood luck with RNG!'
|
||||||
}
|
}
|
||||||
value={party.description}
|
value={props.party?.description}
|
||||||
onChange={handleTextAreaChange}
|
onChange={handleTextAreaChange}
|
||||||
error={errors.description}
|
error={errors.description}
|
||||||
ref={descriptionInput}
|
ref={descriptionInput}
|
||||||
|
|
@ -248,9 +275,9 @@ const PartyDetails = (props: Props) => {
|
||||||
{party.name ? party.name : 'Untitled'}
|
{party.name ? party.name : 'Untitled'}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="attribution">
|
<div className="attribution">
|
||||||
{party.user ? linkedUserBlock(party.user) : userBlock()}
|
{renderUserBlock()}
|
||||||
{party.raid ? linkedRaidBlock(party.raid) : ''}
|
{party.raid ? linkedRaidBlock(party.raid) : ''}
|
||||||
{party.created_at != undefined ? (
|
{party.created_at != '' ? (
|
||||||
<time
|
<time
|
||||||
className="last-updated"
|
className="last-updated"
|
||||||
dateTime={new Date(party.created_at).toString()}
|
dateTime={new Date(party.created_at).toString()}
|
||||||
|
|
@ -284,25 +311,9 @@ const PartyDetails = (props: Props) => {
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|
||||||
const emptyDetails = (
|
|
||||||
<div className={emptyClasses}>
|
|
||||||
{party.editable ? (
|
|
||||||
<Button
|
|
||||||
accessoryIcon={<EditIcon />}
|
|
||||||
text={t('buttons.show_info')}
|
|
||||||
onClick={toggleDetails}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{editable && (party.name || party.description || party.raid)
|
{readOnly}
|
||||||
? readOnly
|
|
||||||
: emptyDetails}
|
|
||||||
{editable}
|
{editable}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
||||||
value={props.currentRaid}
|
value={props.currentRaid}
|
||||||
placeholder={'Select a raid...'}
|
placeholder={'Select a raid...'}
|
||||||
open={open}
|
open={open}
|
||||||
|
onOpenChange={() => setOpen(!open)}
|
||||||
onClick={openRaidSelect}
|
onClick={openRaidSelect}
|
||||||
onValueChange={handleChange}
|
onValueChange={handleChange}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -340,10 +340,24 @@ const SearchModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||||
|
event.preventDefault()
|
||||||
|
openChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOpenAutoFocus(event: Event) {
|
||||||
|
event.preventDefault()
|
||||||
|
if (searchInput.current) searchInput.current.focus()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={openChange}>
|
<Dialog open={open} onOpenChange={openChange}>
|
||||||
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
||||||
<DialogContent className="Search Dialog">
|
<DialogContent
|
||||||
|
className="Search Dialog"
|
||||||
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
|
onOpenAutoFocus={onOpenAutoFocus}
|
||||||
|
>
|
||||||
<div id="Header">
|
<div id="Header">
|
||||||
<div id="Bar">
|
<div id="Bar">
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ interface Props
|
||||||
open: boolean
|
open: boolean
|
||||||
trigger?: React.ReactNode
|
trigger?: React.ReactNode
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
onClick?: () => void
|
onOpenChange?: () => void
|
||||||
onValueChange?: (value: string) => void
|
onValueChange?: (value: string) => void
|
||||||
|
onClose?: () => void
|
||||||
triggerClass?: string
|
triggerClass?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,8 +25,13 @@ const Select = React.forwardRef<HTMLButtonElement, Props>(function Select(
|
||||||
props: Props,
|
props: Props,
|
||||||
forwardedRef
|
forwardedRef
|
||||||
) {
|
) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
const [value, setValue] = useState('')
|
const [value, setValue] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpen(props.open)
|
||||||
|
}, [props.open])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.value && props.value !== '') setValue(`${props.value}`)
|
if (props.value && props.value !== '') setValue(`${props.value}`)
|
||||||
else setValue('')
|
else setValue('')
|
||||||
|
|
@ -36,10 +42,27 @@ const Select = React.forwardRef<HTMLButtonElement, Props>(function Select(
|
||||||
if (props.onValueChange) props.onValueChange(newValue)
|
if (props.onValueChange) props.onValueChange(newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onCloseAutoFocus() {
|
||||||
|
setOpen(false)
|
||||||
|
if (props.onClose) props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEscapeKeyDown() {
|
||||||
|
setOpen(false)
|
||||||
|
if (props.onClose) props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPointerDownOutside() {
|
||||||
|
setOpen(false)
|
||||||
|
if (props.onClose) props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadixSelect.Root
|
<RadixSelect.Root
|
||||||
|
open={open}
|
||||||
value={value !== '' ? value : undefined}
|
value={value !== '' ? value : undefined}
|
||||||
onValueChange={onValueChange}
|
onValueChange={onValueChange}
|
||||||
|
onOpenChange={props.onOpenChange}
|
||||||
>
|
>
|
||||||
<RadixSelect.Trigger
|
<RadixSelect.Trigger
|
||||||
className={classNames('SelectTrigger', props.triggerClass)}
|
className={classNames('SelectTrigger', props.triggerClass)}
|
||||||
|
|
@ -53,7 +76,11 @@ const Select = React.forwardRef<HTMLButtonElement, Props>(function Select(
|
||||||
</RadixSelect.Trigger>
|
</RadixSelect.Trigger>
|
||||||
|
|
||||||
<RadixSelect.Portal className="Select">
|
<RadixSelect.Portal className="Select">
|
||||||
<RadixSelect.Content>
|
<RadixSelect.Content
|
||||||
|
onCloseAutoFocus={onCloseAutoFocus}
|
||||||
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
|
onPointerDownOutside={onPointerDownOutside}
|
||||||
|
>
|
||||||
<RadixSelect.ScrollUpButton className="Scroll Up">
|
<RadixSelect.ScrollUpButton className="Scroll Up">
|
||||||
<ArrowIcon />
|
<ArrowIcon />
|
||||||
</RadixSelect.ScrollUpButton>
|
</RadixSelect.ScrollUpButton>
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ interface Props {
|
||||||
imageClass?: string
|
imageClass?: string
|
||||||
imageSrc?: string[]
|
imageSrc?: string[]
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onClick: () => void
|
onOpenChange: () => void
|
||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectTableField = (props: Props) => {
|
const SelectTableField = (props: Props) => {
|
||||||
|
|
@ -53,8 +54,9 @@ const SelectTableField = (props: Props) => {
|
||||||
<Select
|
<Select
|
||||||
name={props.name}
|
name={props.name}
|
||||||
open={props.open}
|
open={props.open}
|
||||||
onClick={props.onClick}
|
onOpenChange={props.onOpenChange}
|
||||||
onValueChange={props.onChange}
|
onValueChange={props.onChange}
|
||||||
|
onClose={props.onClose}
|
||||||
triggerClass={classNames({ Bound: true, Table: true })}
|
triggerClass={classNames({ Bound: true, Table: true })}
|
||||||
value={value}
|
value={value}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -4,27 +4,6 @@
|
||||||
gap: calc($unit / 2);
|
gap: calc($unit / 2);
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit;
|
||||||
|
|
||||||
.Button {
|
|
||||||
font-size: $font-regular;
|
|
||||||
padding: ($unit * 1.5) ($unit * 2);
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&.btn-disabled {
|
|
||||||
background: $grey-90;
|
|
||||||
color: $grey-70;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.btn-disabled) {
|
|
||||||
background: $grey-90;
|
|
||||||
color: $grey-50;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $grey-80;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.terms {
|
.terms {
|
||||||
color: $grey-50;
|
color: $grey-50;
|
||||||
font-size: $font-small;
|
font-size: $font-small;
|
||||||
|
|
@ -40,8 +19,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
|
||||||
background: $grey-90;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,15 @@ const SignupModal = (props: Props) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOpenAutoFocus(event: Event) {
|
||||||
|
event.preventDefault()
|
||||||
|
if (usernameInput.current) usernameInput.current.focus()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={openChange}>
|
<Dialog open={open} onOpenChange={openChange}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
|
|
@ -270,7 +279,11 @@ const SignupModal = (props: Props) => {
|
||||||
<span>{t('menu.signup')}</span>
|
<span>{t('menu.signup')}</span>
|
||||||
</li>
|
</li>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="Signup Dialog">
|
<DialogContent
|
||||||
|
className="Signup Dialog"
|
||||||
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
|
onOpenAutoFocus={onOpenAutoFocus}
|
||||||
|
>
|
||||||
<div className="DialogHeader">
|
<div className="DialogHeader">
|
||||||
<div className="DialogTitle">
|
<div className="DialogTitle">
|
||||||
<h1>{t('modals.signup.title')}</h1>
|
<h1>{t('modals.signup.title')}</h1>
|
||||||
|
|
@ -315,7 +328,10 @@ const SignupModal = (props: Props) => {
|
||||||
ref={passwordConfirmationInput}
|
ref={passwordConfirmationInput}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button text={t('modals.signup.buttons.confirm')} />
|
<Button
|
||||||
|
disabled={!formValid}
|
||||||
|
text={t('modals.signup.buttons.confirm')}
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="terms">
|
<p className="terms">
|
||||||
{/* <Trans i18nKey="modals.signup.agreement">
|
{/* <Trans i18nKey="modals.signup.agreement">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import UncapStar from '~components/UncapStar'
|
import UncapStar from '~components/UncapStar'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
@ -10,12 +10,10 @@ interface Props {
|
||||||
flb: boolean
|
flb: boolean
|
||||||
ulb: boolean
|
ulb: boolean
|
||||||
special: boolean
|
special: boolean
|
||||||
updateUncap?: (uncap: number) => void
|
updateUncap?: (index: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const UncapIndicator = (props: Props) => {
|
const UncapIndicator = (props: Props) => {
|
||||||
const [uncap, setUncap] = useState(props.uncapLevel)
|
|
||||||
|
|
||||||
const numStars = setNumStars()
|
const numStars = setNumStars()
|
||||||
function setNumStars() {
|
function setNumStars() {
|
||||||
let numStars
|
let numStars
|
||||||
|
|
@ -53,7 +51,7 @@ const UncapIndicator = (props: Props) => {
|
||||||
|
|
||||||
function toggleStar(index: number, empty: boolean) {
|
function toggleStar(index: number, empty: boolean) {
|
||||||
if (props.updateUncap) {
|
if (props.updateUncap) {
|
||||||
if (empty) props.updateUncap(index + 1)
|
if (empty && index > 0) props.updateUncap(index + 1)
|
||||||
else props.updateUncap(index)
|
else props.updateUncap(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -71,10 +69,11 @@ const UncapIndicator = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ulb = (i: number) => {
|
const ulb = (i: number) => {
|
||||||
|
// console.log('ULB; Number of stars:', props.uncapLevel)
|
||||||
return (
|
return (
|
||||||
<UncapStar
|
<UncapStar
|
||||||
ulb={true}
|
ulb={true}
|
||||||
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
|
||||||
key={`star_${i}`}
|
key={`star_${i}`}
|
||||||
index={i}
|
index={i}
|
||||||
onClick={toggleStar}
|
onClick={toggleStar}
|
||||||
|
|
@ -83,10 +82,11 @@ const UncapIndicator = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const flb = (i: number) => {
|
const flb = (i: number) => {
|
||||||
|
// console.log('FLB; Number of stars:', props.uncapLevel)
|
||||||
return (
|
return (
|
||||||
<UncapStar
|
<UncapStar
|
||||||
flb={true}
|
flb={true}
|
||||||
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
|
||||||
key={`star_${i}`}
|
key={`star_${i}`}
|
||||||
index={i}
|
index={i}
|
||||||
onClick={toggleStar}
|
onClick={toggleStar}
|
||||||
|
|
@ -95,10 +95,10 @@ const UncapIndicator = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mlb = (i: number) => {
|
const mlb = (i: number) => {
|
||||||
// console.log("MLB; Number of stars:", props.uncapLevel)
|
// console.log('MLB; Number of stars:', props.uncapLevel)
|
||||||
return (
|
return (
|
||||||
<UncapStar
|
<UncapStar
|
||||||
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
|
||||||
key={`star_${i}`}
|
key={`star_${i}`}
|
||||||
index={i}
|
index={i}
|
||||||
onClick={toggleStar}
|
onClick={toggleStar}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ const WeaponGrid = (props: Props) => {
|
||||||
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
||||||
|
|
||||||
saveWeapon(party.id, weapon, position).then((response) =>
|
saveWeapon(party.id, weapon, position).then((response) =>
|
||||||
storeGridWeapon(response.data)
|
storeGridWeapon(response.data.grid_weapon)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -252,6 +252,7 @@ const WeaponGrid = (props: Props) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||||
|
console.log(`Updating uncap level at position ${position} to ${uncapLevel}`)
|
||||||
if (appState.grid.weapons.mainWeapon && position == -1)
|
if (appState.grid.weapons.mainWeapon && position == -1)
|
||||||
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel
|
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,13 @@ import './index.scss'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
open: boolean
|
||||||
currentValue?: WeaponKey
|
currentValue?: WeaponKey
|
||||||
series: number
|
series: number
|
||||||
slot: number
|
slot: number
|
||||||
onChange?: (value: string, slot: number) => void
|
onChange?: (value: string, slot: number) => void
|
||||||
|
onOpenChange: () => void
|
||||||
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const WeaponKeySelect = React.forwardRef<HTMLButtonElement, Props>(
|
const WeaponKeySelect = React.forwardRef<HTMLButtonElement, Props>(
|
||||||
|
|
@ -64,10 +67,6 @@ const WeaponKeySelect = React.forwardRef<HTMLButtonElement, Props>(
|
||||||
fetchWeaponKeys()
|
fetchWeaponKeys()
|
||||||
}, [props.series, props.slot])
|
}, [props.series, props.slot])
|
||||||
|
|
||||||
function openSelect() {
|
|
||||||
setOpen(!open)
|
|
||||||
}
|
|
||||||
|
|
||||||
function weaponKeyGroup(index: number) {
|
function weaponKeyGroup(index: number) {
|
||||||
;['α', 'β', 'γ', 'Δ'].sort((a, b) => a.localeCompare(b, 'el'))
|
;['α', 'β', 'γ', 'Δ'].sort((a, b) => a.localeCompare(b, 'el'))
|
||||||
|
|
||||||
|
|
@ -125,9 +124,10 @@ const WeaponKeySelect = React.forwardRef<HTMLButtonElement, Props>(
|
||||||
<Select
|
<Select
|
||||||
key={`weapon-key-${props.slot}`}
|
key={`weapon-key-${props.slot}`}
|
||||||
value={props.currentValue ? props.currentValue.id : 'no-key'}
|
value={props.currentValue ? props.currentValue.id : 'no-key'}
|
||||||
open={open}
|
open={props.open}
|
||||||
|
onClose={props.onClose}
|
||||||
|
onOpenChange={props.onOpenChange}
|
||||||
onValueChange={handleChange}
|
onValueChange={handleChange}
|
||||||
onClick={openSelect}
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
triggerClass="modal"
|
triggerClass="modal"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,13 @@ import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { AxiosResponse } from 'axios'
|
import { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
import * as Dialog from '@radix-ui/react-dialog'
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '~components/Dialog'
|
||||||
|
|
||||||
import AXSelect from '~components/AxSelect'
|
import AXSelect from '~components/AxSelect'
|
||||||
import AwakeningSelect from '~components/AwakeningSelect'
|
import AwakeningSelect from '~components/AwakeningSelect'
|
||||||
|
|
@ -74,6 +80,14 @@ const WeaponModal = (props: Props) => {
|
||||||
const [weaponKey2Id, setWeaponKey2Id] = useState('')
|
const [weaponKey2Id, setWeaponKey2Id] = useState('')
|
||||||
const [weaponKey3Id, setWeaponKey3Id] = useState('')
|
const [weaponKey3Id, setWeaponKey3Id] = useState('')
|
||||||
|
|
||||||
|
const [weaponKey1Open, setWeaponKey1Open] = useState(false)
|
||||||
|
const [weaponKey2Open, setWeaponKey2Open] = useState(false)
|
||||||
|
const [weaponKey3Open, setWeaponKey3Open] = useState(false)
|
||||||
|
const [weaponKey4Open, setWeaponKey4Open] = useState(false)
|
||||||
|
const [ax1Open, setAx1Open] = useState(false)
|
||||||
|
const [ax2Open, setAx2Open] = useState(false)
|
||||||
|
const [awakeningOpen, setAwakeningOpen] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setElement(props.gridWeapon.element)
|
setElement(props.gridWeapon.element)
|
||||||
|
|
||||||
|
|
@ -188,16 +202,35 @@ const WeaponModal = (props: Props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openSelect(index: 1 | 2 | 3 | 4) {
|
||||||
|
setWeaponKey1Open(index === 1 ? !weaponKey1Open : false)
|
||||||
|
setWeaponKey2Open(index === 2 ? !weaponKey2Open : false)
|
||||||
|
setWeaponKey3Open(index === 3 ? !weaponKey3Open : false)
|
||||||
|
setWeaponKey4Open(index === 4 ? !weaponKey4Open : false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function receiveAxOpen(index: 1 | 2, isOpen: boolean) {
|
||||||
|
if (index === 1) setAx1Open(isOpen)
|
||||||
|
if (index === 2) setAx2Open(isOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
function receiveAwakeningOpen(isOpen: boolean) {
|
||||||
|
setAwakeningOpen(isOpen)
|
||||||
|
}
|
||||||
|
|
||||||
const keySelect = () => {
|
const keySelect = () => {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h3>{t('modals.weapon.subtitles.weapon_keys')}</h3>
|
<h3>{t('modals.weapon.subtitles.weapon_keys')}</h3>
|
||||||
{[2, 3, 17, 22].includes(props.gridWeapon.object.series) ? (
|
{[2, 3, 17, 22].includes(props.gridWeapon.object.series) ? (
|
||||||
<WeaponKeySelect
|
<WeaponKeySelect
|
||||||
|
open={weaponKey1Open}
|
||||||
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
|
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
|
||||||
series={props.gridWeapon.object.series}
|
series={props.gridWeapon.object.series}
|
||||||
slot={0}
|
slot={0}
|
||||||
|
onOpenChange={() => openSelect(1)}
|
||||||
onChange={receiveWeaponKey}
|
onChange={receiveWeaponKey}
|
||||||
|
onClose={() => setWeaponKey1Open(false)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
|
|
@ -205,10 +238,13 @@ const WeaponModal = (props: Props) => {
|
||||||
|
|
||||||
{[2, 3, 17].includes(props.gridWeapon.object.series) ? (
|
{[2, 3, 17].includes(props.gridWeapon.object.series) ? (
|
||||||
<WeaponKeySelect
|
<WeaponKeySelect
|
||||||
|
open={weaponKey2Open}
|
||||||
currentValue={weaponKey2 != null ? weaponKey2 : undefined}
|
currentValue={weaponKey2 != null ? weaponKey2 : undefined}
|
||||||
series={props.gridWeapon.object.series}
|
series={props.gridWeapon.object.series}
|
||||||
slot={1}
|
slot={1}
|
||||||
|
onOpenChange={() => openSelect(2)}
|
||||||
onChange={receiveWeaponKey}
|
onChange={receiveWeaponKey}
|
||||||
|
onClose={() => setWeaponKey2Open(false)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
|
|
@ -216,10 +252,13 @@ const WeaponModal = (props: Props) => {
|
||||||
|
|
||||||
{props.gridWeapon.object.series == 17 ? (
|
{props.gridWeapon.object.series == 17 ? (
|
||||||
<WeaponKeySelect
|
<WeaponKeySelect
|
||||||
|
open={weaponKey3Open}
|
||||||
currentValue={weaponKey3 != null ? weaponKey3 : undefined}
|
currentValue={weaponKey3 != null ? weaponKey3 : undefined}
|
||||||
series={props.gridWeapon.object.series}
|
series={props.gridWeapon.object.series}
|
||||||
slot={2}
|
slot={2}
|
||||||
|
onOpenChange={() => openSelect(3)}
|
||||||
onChange={receiveWeaponKey}
|
onChange={receiveWeaponKey}
|
||||||
|
onClose={() => setWeaponKey3Open(false)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
|
|
@ -228,10 +267,13 @@ const WeaponModal = (props: Props) => {
|
||||||
{props.gridWeapon.object.series == 24 &&
|
{props.gridWeapon.object.series == 24 &&
|
||||||
props.gridWeapon.object.uncap.ulb ? (
|
props.gridWeapon.object.uncap.ulb ? (
|
||||||
<WeaponKeySelect
|
<WeaponKeySelect
|
||||||
|
open={weaponKey4Open}
|
||||||
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
|
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
|
||||||
series={props.gridWeapon.object.series}
|
series={props.gridWeapon.object.series}
|
||||||
slot={0}
|
slot={0}
|
||||||
|
onOpenChange={() => openSelect(4)}
|
||||||
onChange={receiveWeaponKey}
|
onChange={receiveWeaponKey}
|
||||||
|
onClose={() => setWeaponKey4Open(false)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
|
|
@ -247,6 +289,7 @@ const WeaponModal = (props: Props) => {
|
||||||
<AXSelect
|
<AXSelect
|
||||||
axType={props.gridWeapon.object.ax}
|
axType={props.gridWeapon.object.ax}
|
||||||
currentSkills={props.gridWeapon.ax}
|
currentSkills={props.gridWeapon.ax}
|
||||||
|
onOpenChange={receiveAxOpen}
|
||||||
sendValidity={receiveValidity}
|
sendValidity={receiveValidity}
|
||||||
sendValues={receiveAxValues}
|
sendValues={receiveAxValues}
|
||||||
/>
|
/>
|
||||||
|
|
@ -262,6 +305,7 @@ const WeaponModal = (props: Props) => {
|
||||||
object="weapon"
|
object="weapon"
|
||||||
awakeningType={props.gridWeapon.awakening?.type}
|
awakeningType={props.gridWeapon.awakening?.type}
|
||||||
awakeningLevel={props.gridWeapon.awakening?.level}
|
awakeningLevel={props.gridWeapon.awakening?.level}
|
||||||
|
onOpenChange={receiveAwakeningOpen}
|
||||||
sendValidity={receiveValidity}
|
sendValidity={receiveValidity}
|
||||||
sendValues={receiveAwakeningValues}
|
sendValues={receiveAwakeningValues}
|
||||||
/>
|
/>
|
||||||
|
|
@ -278,49 +322,64 @@ const WeaponModal = (props: Props) => {
|
||||||
setOpen(open)
|
setOpen(open)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const anySelectOpen =
|
||||||
|
weaponKey1Open ||
|
||||||
|
weaponKey2Open ||
|
||||||
|
weaponKey3Open ||
|
||||||
|
weaponKey4Open ||
|
||||||
|
ax1Open ||
|
||||||
|
ax2Open ||
|
||||||
|
awakeningOpen
|
||||||
|
|
||||||
|
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||||
|
if (anySelectOpen) {
|
||||||
|
return event.preventDefault()
|
||||||
|
} else {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// TODO: Refactor into Dialog component
|
// TODO: Refactor into Dialog component
|
||||||
<Dialog.Root open={open} onOpenChange={openChange}>
|
<Dialog open={open} onOpenChange={openChange}>
|
||||||
<Dialog.Trigger asChild>{props.children}</Dialog.Trigger>
|
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
||||||
<Dialog.Portal>
|
<DialogContent
|
||||||
<Dialog.Content
|
className="Weapon Dialog"
|
||||||
className="Weapon Dialog"
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onEscapeKeyDown={onEscapeKeyDown}
|
||||||
>
|
>
|
||||||
<div className="DialogHeader">
|
<div className="DialogHeader">
|
||||||
<div className="DialogTop">
|
<div className="DialogTop">
|
||||||
<Dialog.Title className="SubTitle">
|
<DialogTitle className="SubTitle">
|
||||||
{t('modals.weapon.title')}
|
{t('modals.weapon.title')}
|
||||||
</Dialog.Title>
|
</DialogTitle>
|
||||||
<Dialog.Title className="DialogTitle">
|
<DialogTitle className="DialogTitle">
|
||||||
{props.gridWeapon.object.name[locale]}
|
{props.gridWeapon.object.name[locale]}
|
||||||
</Dialog.Title>
|
</DialogTitle>
|
||||||
</div>
|
|
||||||
<Dialog.Close className="DialogClose" asChild>
|
|
||||||
<span>
|
|
||||||
<CrossIcon />
|
|
||||||
</span>
|
|
||||||
</Dialog.Close>
|
|
||||||
</div>
|
</div>
|
||||||
|
<DialogClose className="DialogClose" asChild>
|
||||||
|
<span>
|
||||||
|
<CrossIcon />
|
||||||
|
</span>
|
||||||
|
</DialogClose>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mods">
|
<div className="mods">
|
||||||
{props.gridWeapon.object.element == 0 ? elementSelect() : ''}
|
{props.gridWeapon.object.element == 0 ? elementSelect() : ''}
|
||||||
{[2, 3, 17, 24].includes(props.gridWeapon.object.series)
|
{[2, 3, 17, 24].includes(props.gridWeapon.object.series)
|
||||||
? keySelect()
|
? keySelect()
|
||||||
: ''}
|
: ''}
|
||||||
{props.gridWeapon.object.ax > 0 ? axSelect() : ''}
|
{props.gridWeapon.object.ax > 0 ? axSelect() : ''}
|
||||||
{props.gridWeapon.awakening ? awakeningSelect() : ''}
|
{props.gridWeapon.awakening ? awakeningSelect() : ''}
|
||||||
<Button
|
<Button
|
||||||
contained={true}
|
contained={true}
|
||||||
onClick={updateWeapon}
|
onClick={updateWeapon}
|
||||||
disabled={!formValid}
|
disabled={!formValid}
|
||||||
text={t('modals.weapon.buttons.confirm')}
|
text={t('modals.weapon.buttons.confirm')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Content>
|
</DialogContent>
|
||||||
<Dialog.Overlay className="Overlay" />
|
</Dialog>
|
||||||
</Dialog.Portal>
|
|
||||||
</Dialog.Root>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,33 +25,6 @@ const Proficiency = [
|
||||||
'gun',
|
'gun',
|
||||||
'katana',
|
'katana',
|
||||||
]
|
]
|
||||||
const Series = [
|
|
||||||
'seraphic',
|
|
||||||
'grand',
|
|
||||||
'opus',
|
|
||||||
'draconic',
|
|
||||||
'revenant',
|
|
||||||
'primal',
|
|
||||||
'beast',
|
|
||||||
'regalia',
|
|
||||||
'omega',
|
|
||||||
'olden_primal',
|
|
||||||
'hollowsky',
|
|
||||||
'xeno',
|
|
||||||
'astral',
|
|
||||||
'rose',
|
|
||||||
'ultima',
|
|
||||||
'bahamut',
|
|
||||||
'epic',
|
|
||||||
'ennead',
|
|
||||||
'cosmos',
|
|
||||||
'ancestral',
|
|
||||||
'superlative',
|
|
||||||
'vintage',
|
|
||||||
'class_champion',
|
|
||||||
'sephira',
|
|
||||||
'new_world_foundation',
|
|
||||||
]
|
|
||||||
|
|
||||||
const WeaponResult = (props: Props) => {
|
const WeaponResult = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,8 @@ import {
|
||||||
emptyRarityState,
|
emptyRarityState,
|
||||||
emptyWeaponSeriesState,
|
emptyWeaponSeriesState,
|
||||||
} from '~utils/emptyStates'
|
} from '~utils/emptyStates'
|
||||||
import {
|
import { elements, proficiencies, rarities } from '~utils/stateValues'
|
||||||
elements,
|
import { weaponSeries } from '~utils/weaponSeries'
|
||||||
proficiencies,
|
|
||||||
rarities,
|
|
||||||
weaponSeries,
|
|
||||||
} from '~utils/stateValues'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sendFilters: (filters: { [key: string]: number[] }) => void
|
sendFilters: (filters: { [key: string]: number[] }) => void
|
||||||
|
|
@ -128,6 +124,45 @@ const WeaponSearchFilterBar = (props: Props) => {
|
||||||
props.sendFilters(filters)
|
props.sendFilters(filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderWeaponSeries = () => {
|
||||||
|
const numColumns = 3
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{Array.from({ length: numColumns }, () => 0).map((x, i) => {
|
||||||
|
return renderWeaponSeriesGroup(i)
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderWeaponSeriesGroup = (index: number) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Group className="Group">
|
||||||
|
{weaponSeries
|
||||||
|
.slice(
|
||||||
|
index * Math.ceil(weaponSeries.length / 3),
|
||||||
|
(index + 1) * Math.ceil(weaponSeries.length / 3)
|
||||||
|
)
|
||||||
|
.map((x, i) => {
|
||||||
|
return renderSingleWeaponSeries(x.id, x.slug)
|
||||||
|
})}
|
||||||
|
</DropdownMenu.Group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderSingleWeaponSeries = (id: number, slug: string) => {
|
||||||
|
return (
|
||||||
|
<SearchFilterCheckboxItem
|
||||||
|
key={id}
|
||||||
|
onCheckedChange={handleSeriesChange}
|
||||||
|
checked={seriesState[slug].checked}
|
||||||
|
valueKey={slug}
|
||||||
|
>
|
||||||
|
{t(`series.${slug}`)}
|
||||||
|
</SearchFilterCheckboxItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sendFilters()
|
sendFilters()
|
||||||
}, [rarityState, elementState, proficiencyState, seriesState])
|
}, [rarityState, elementState, proficiencyState, seriesState])
|
||||||
|
|
@ -254,58 +289,7 @@ const WeaponSearchFilterBar = (props: Props) => {
|
||||||
<DropdownMenu.Label className="Label">
|
<DropdownMenu.Label className="Label">
|
||||||
{t('filters.labels.series')}
|
{t('filters.labels.series')}
|
||||||
</DropdownMenu.Label>
|
</DropdownMenu.Label>
|
||||||
<section>
|
<section>{renderWeaponSeries()}</section>
|
||||||
<DropdownMenu.Group className="Group">
|
|
||||||
{Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
|
||||||
return (
|
|
||||||
<SearchFilterCheckboxItem
|
|
||||||
key={weaponSeries[i]}
|
|
||||||
onCheckedChange={handleSeriesChange}
|
|
||||||
checked={seriesState[weaponSeries[i]].checked}
|
|
||||||
valueKey={weaponSeries[i]}
|
|
||||||
>
|
|
||||||
{t(`series.${weaponSeries[i]}`)}
|
|
||||||
</SearchFilterCheckboxItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</DropdownMenu.Group>
|
|
||||||
<DropdownMenu.Group className="Group">
|
|
||||||
{Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
|
||||||
return (
|
|
||||||
<SearchFilterCheckboxItem
|
|
||||||
key={weaponSeries[i + weaponSeries.length / 3]}
|
|
||||||
onCheckedChange={handleSeriesChange}
|
|
||||||
checked={
|
|
||||||
seriesState[weaponSeries[i + weaponSeries.length / 3]]
|
|
||||||
.checked
|
|
||||||
}
|
|
||||||
valueKey={weaponSeries[i + weaponSeries.length / 3]}
|
|
||||||
>
|
|
||||||
{t(`series.${weaponSeries[i + weaponSeries.length / 3]}`)}
|
|
||||||
</SearchFilterCheckboxItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</DropdownMenu.Group>
|
|
||||||
<DropdownMenu.Group className="Group">
|
|
||||||
{Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
|
||||||
return (
|
|
||||||
<SearchFilterCheckboxItem
|
|
||||||
key={weaponSeries[i + 2 * (weaponSeries.length / 3)]}
|
|
||||||
onCheckedChange={handleSeriesChange}
|
|
||||||
checked={
|
|
||||||
seriesState[weaponSeries[i + 2 * (weaponSeries.length / 3)]]
|
|
||||||
.checked
|
|
||||||
}
|
|
||||||
valueKey={weaponSeries[i + 2 * (weaponSeries.length / 3)]}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
`series.${weaponSeries[i + 2 * (weaponSeries.length / 3)]}`
|
|
||||||
)}
|
|
||||||
</SearchFilterCheckboxItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</DropdownMenu.Group>
|
|
||||||
</section>
|
|
||||||
</SearchFilter>
|
</SearchFilter>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import Button from '~components/Button'
|
||||||
|
|
||||||
import type { SearchableObject } from '~types'
|
import type { SearchableObject } from '~types'
|
||||||
|
|
||||||
import { appState } from '~utils/appState'
|
|
||||||
import { axData } from '~utils/axData'
|
import { axData } from '~utils/axData'
|
||||||
import { weaponAwakening } from '~utils/awakening'
|
import { weaponAwakening } from '~utils/awakening'
|
||||||
|
|
||||||
|
|
@ -341,9 +340,9 @@ const WeaponUnit = (props: Props) => {
|
||||||
} else return
|
} else return
|
||||||
}
|
}
|
||||||
|
|
||||||
function passUncapData(uncap: number) {
|
function passUncapData(index: number) {
|
||||||
if (props.gridWeapon)
|
if (props.gridWeapon)
|
||||||
props.updateUncap(props.gridWeapon.id, props.position, uncap)
|
props.updateUncap(props.gridWeapon.id, props.position, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
function canBeModified(gridWeapon: GridWeapon) {
|
function canBeModified(gridWeapon: GridWeapon) {
|
||||||
|
|
|
||||||
|
|
@ -246,15 +246,27 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div id="Profile">
|
<div id="Profile">
|
||||||
<Head>
|
<Head>
|
||||||
<title>@{props.user?.username}'s Teams</title>
|
{/* HTML */}
|
||||||
|
<title>
|
||||||
|
{t('page.titles.profile', { username: props.user?.username })}
|
||||||
|
</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content={t('page.descriptions.profile', {
|
||||||
|
username: props.user?.username,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* OpenGraph */}
|
||||||
<meta
|
<meta
|
||||||
property="og:title"
|
property="og:title"
|
||||||
content={`@${props.user?.username}\'s Teams`}
|
content={t('page.titles.profile', { username: props.user?.username })}
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content={`Browse @${props.user?.username}\'s Teams and filter raid, element or recency`}
|
content={t('page.descriptions.profile', {
|
||||||
|
username: props.user?.username,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
property="og:url"
|
property="og:url"
|
||||||
|
|
@ -262,15 +274,18 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
|
{/* Twitter */}
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta property="twitter:domain" content="app.granblue.team" />
|
<meta property="twitter:domain" content="app.granblue.team" />
|
||||||
<meta
|
<meta
|
||||||
name="twitter:title"
|
name="twitter:title"
|
||||||
content={`@${props.user?.username}\'s Teams`}
|
content={t('page.titles.profile', { username: props.user?.username })}
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
name="twitter:description"
|
name="twitter:description"
|
||||||
content={`Browse @${props.user?.username}\''s Teams and filter raid, element or recency`}
|
content={t('page.descriptions.profile', {
|
||||||
|
username: props.user?.username,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
<FilterBar
|
<FilterBar
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { getCookie } from 'cookies-next'
|
import Head from 'next/head'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
|
||||||
import Party from '~components/Party'
|
import Party from '~components/Party'
|
||||||
|
|
@ -12,6 +13,9 @@ import api from '~utils/api'
|
||||||
|
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import type { GroupedWeaponKeys } from '~utils/groupWeaponKeys'
|
import type { GroupedWeaponKeys } from '~utils/groupWeaponKeys'
|
||||||
|
import { useSnapshot } from 'valtio'
|
||||||
|
import { raidGroups } from '~utils/raidGroups'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
party: Party
|
party: Party
|
||||||
|
|
@ -20,9 +24,18 @@ interface Props {
|
||||||
raids: Raid[]
|
raids: Raid[]
|
||||||
sortedRaids: Raid[][]
|
sortedRaids: Raid[][]
|
||||||
weaponKeys: GroupedWeaponKeys
|
weaponKeys: GroupedWeaponKeys
|
||||||
|
meta: { [key: string]: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
const PartyRoute: React.FC<Props> = (props: Props) => {
|
const PartyRoute: React.FC<Props> = (props: Props) => {
|
||||||
|
// Import translations
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
|
// Set up router
|
||||||
|
const router = useRouter()
|
||||||
|
const locale =
|
||||||
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
persistStaticData()
|
persistStaticData()
|
||||||
}, [persistStaticData])
|
}, [persistStaticData])
|
||||||
|
|
@ -34,7 +47,65 @@ const PartyRoute: React.FC<Props> = (props: Props) => {
|
||||||
appState.weaponKeys = props.weaponKeys
|
appState.weaponKeys = props.weaponKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Party team={props.party} raids={props.sortedRaids} />
|
function generateTitle() {
|
||||||
|
const teamName =
|
||||||
|
props.party && props.party.name ? props.party.name : t('no_title')
|
||||||
|
const username = props.party
|
||||||
|
? `@${props.party.user.username}`
|
||||||
|
: t('no_user')
|
||||||
|
|
||||||
|
const title = t('page.titles.team', {
|
||||||
|
username: username,
|
||||||
|
teamName: teamName,
|
||||||
|
emoji: props.meta.element,
|
||||||
|
})
|
||||||
|
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Party team={props.party} raids={props.sortedRaids} />
|
||||||
|
<Head>
|
||||||
|
{/* HTML */}
|
||||||
|
<title>{generateTitle()}</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content={t('page.descriptions.team', {
|
||||||
|
username: props.party.user?.username,
|
||||||
|
raidName: props.party.raid.name[locale],
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* OpenGraph */}
|
||||||
|
<meta property="og:title" content={generateTitle()} />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content={t('page.descriptions.team', {
|
||||||
|
username: props.party.user?.username,
|
||||||
|
raidName: props.party.raid.name[locale],
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:url"
|
||||||
|
content={`https://app.granblue.team/p/${props.party.shortcode}`}
|
||||||
|
/>
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
|
{/* Twitter */}
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta property="twitter:domain" content="app.granblue.team" />
|
||||||
|
<meta name="twitter:title" content={generateTitle()} />
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content={t('page.descriptions.team', {
|
||||||
|
username: props.party.user?.username,
|
||||||
|
raidName: props.party.raid.name[locale],
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServerSidePaths = async () => {
|
export const getServerSidePaths = async () => {
|
||||||
|
|
@ -80,6 +151,31 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
console.log('No party code')
|
console.log('No party code')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getElement() {
|
||||||
|
if (party) {
|
||||||
|
const mainhand = party.weapons.find((weapon) => weapon.mainhand)
|
||||||
|
if (mainhand && mainhand.object.element === 0) {
|
||||||
|
return mainhand.element
|
||||||
|
} else {
|
||||||
|
return mainhand?.object.element
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementEmoji() {
|
||||||
|
const element = getElement()
|
||||||
|
|
||||||
|
if (element === 0) return '⚪'
|
||||||
|
if (element === 1) return '🟢'
|
||||||
|
if (element === 2) return '🔴'
|
||||||
|
if (element === 3) return '🔵'
|
||||||
|
if (element === 4) return '🟤'
|
||||||
|
if (element === 5) return '🟣'
|
||||||
|
if (element === 6) return '🟡'
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
party: party,
|
party: party,
|
||||||
|
|
@ -88,6 +184,9 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
raids: raids,
|
raids: raids,
|
||||||
sortedRaids: sortedRaids,
|
sortedRaids: sortedRaids,
|
||||||
weaponKeys: weaponKeys,
|
weaponKeys: weaponKeys,
|
||||||
|
meta: {
|
||||||
|
element: elementEmoji()
|
||||||
|
},
|
||||||
...(await serverSideTranslations(locale, ['common'])),
|
...(await serverSideTranslations(locale, ['common'])),
|
||||||
// Will be passed to the page component as props
|
// Will be passed to the page component as props
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -288,15 +288,15 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div id="Teams">
|
<div id="Teams">
|
||||||
<Head>
|
<Head>
|
||||||
<title>{t('saved.title')}</title>
|
<title>{t('page.titles.saved')}</title>
|
||||||
|
|
||||||
<meta property="og:title" content="Your saved Teams" />
|
<meta property="og:title" content={t('page.titles.saved')} />
|
||||||
<meta property="og:url" content="https://app.granblue.team/saved" />
|
<meta property="og:url" content="https://app.granblue.team/saved" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta property="twitter:domain" content="app.granblue.team" />
|
<meta property="twitter:domain" content="app.granblue.team" />
|
||||||
<meta name="twitter:title" content="Your saved Teams" />
|
<meta name="twitter:title" content={t('page.titles.saved')} />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<FilterBar
|
<FilterBar
|
||||||
|
|
|
||||||
|
|
@ -287,22 +287,26 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div id="Teams">
|
<div id="Teams">
|
||||||
<Head>
|
<Head>
|
||||||
<title>{t('teams.title')}</title>
|
{/* HTML */}
|
||||||
|
<title>{t('page.titles.discover')}</title>
|
||||||
|
<meta name="description" content={t('page.descriptions.discover')} />
|
||||||
|
|
||||||
<meta property="og:title" content="Discover Teams" />
|
{/* OpenGraph */}
|
||||||
|
<meta property="og:title" content={t('page.titles.discover')} />
|
||||||
<meta
|
<meta
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="Find different Granblue Fantasy teams by raid, element or recency"
|
content={t('page.descriptions.discover')}
|
||||||
/>
|
/>
|
||||||
<meta property="og:url" content="https://app.granblue.team/teams" />
|
<meta property="og:url" content="https://app.granblue.team/teams" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
|
{/* Twitter */}
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta property="twitter:domain" content="app.granblue.team" />
|
<meta property="twitter:domain" content="app.granblue.team" />
|
||||||
<meta name="twitter:title" content="Discover Teams" />
|
<meta name="twitter:title" content={t('page.titles.discover')} />
|
||||||
<meta
|
<meta
|
||||||
name="twitter:description"
|
name="twitter:description"
|
||||||
content="Find different Granblue Fantasy teams by raid, element or recency"
|
content={t('page.descriptions.discover')}
|
||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@
|
||||||
"bahamut": "Bahamut",
|
"bahamut": "Bahamut",
|
||||||
"epic": "Epic",
|
"epic": "Epic",
|
||||||
"ennead": "Ennead",
|
"ennead": "Ennead",
|
||||||
"cosmos": "Cosmos",
|
"cosmic": "Cosmic",
|
||||||
"ancestral": "Ancestral",
|
"ancestral": "Ancestral",
|
||||||
"superlative": "Superlative",
|
"superlative": "Superlative",
|
||||||
"vintage": "Vintage",
|
"vintage": "Vintage",
|
||||||
|
|
@ -109,7 +109,8 @@
|
||||||
"new_world": "New World Foundation",
|
"new_world": "New World Foundation",
|
||||||
"revenant": "Revenant",
|
"revenant": "Revenant",
|
||||||
"proving": "Proving Grounds",
|
"proving": "Proving Grounds",
|
||||||
"disaster": "Disaster"
|
"disaster": "Disaster",
|
||||||
|
"illustrious": "Illustrious"
|
||||||
},
|
},
|
||||||
"recency": {
|
"recency": {
|
||||||
"all_time": "All time",
|
"all_time": "All time",
|
||||||
|
|
@ -277,6 +278,19 @@
|
||||||
"loading": "Loading teams...",
|
"loading": "Loading teams...",
|
||||||
"not_found": "No teams found"
|
"not_found": "No teams found"
|
||||||
},
|
},
|
||||||
|
"page": {
|
||||||
|
"titles": {
|
||||||
|
"discover": "Discover teams",
|
||||||
|
"profile": "@{{username}}'s Teams / granblue.team",
|
||||||
|
"team": "{{emoji}} {{teamName}} by {{username}} / granblue.team",
|
||||||
|
"saved": "Your saved teams"
|
||||||
|
},
|
||||||
|
"descriptions": {
|
||||||
|
"discover": "Save and discover teams to use in Granblue Fantasy and search by raid, element or recency",
|
||||||
|
"profile": "Browse @{{username}}'s Teams and filter by raid, element or recency",
|
||||||
|
"team": "Browse this team for {{raidName}} by {{username}} and others on granblue.team"
|
||||||
|
}
|
||||||
|
},
|
||||||
"job_skills": {
|
"job_skills": {
|
||||||
"all": "All skills",
|
"all": "All skills",
|
||||||
"buffing": "Buffing",
|
"buffing": "Buffing",
|
||||||
|
|
@ -294,5 +308,6 @@
|
||||||
"coming_soon": "Coming Soon",
|
"coming_soon": "Coming Soon",
|
||||||
"no_title": "Untitled",
|
"no_title": "Untitled",
|
||||||
"no_raid": "No raid",
|
"no_raid": "No raid",
|
||||||
"no_user": "Anonymous"
|
"no_user": "Anonymous",
|
||||||
|
"no_job": "No class"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@
|
||||||
"bahamut": "バハムートウェポン",
|
"bahamut": "バハムートウェポン",
|
||||||
"epic": "エピックウェポン",
|
"epic": "エピックウェポン",
|
||||||
"ennead": "エニアドシリーズ",
|
"ennead": "エニアドシリーズ",
|
||||||
"cosmos": "コスモスシリーズ",
|
"cosmic": "コスモスシリーズ",
|
||||||
"ancestral": "アンセスタルシリーズ",
|
"ancestral": "アンセスタルシリーズ",
|
||||||
"superlative": "スペリオシリーズ",
|
"superlative": "スペリオシリーズ",
|
||||||
"vintage": "ヴィンテージシリーズ",
|
"vintage": "ヴィンテージシリーズ",
|
||||||
|
|
@ -109,7 +109,8 @@
|
||||||
"new_world": "新世界の礎",
|
"new_world": "新世界の礎",
|
||||||
"revenant": "天星器",
|
"revenant": "天星器",
|
||||||
"proving": "ブレイブグラウンド",
|
"proving": "ブレイブグラウンド",
|
||||||
"disaster": "災害シリーズ"
|
"disaster": "災害シリーズ",
|
||||||
|
"luminous": "ルミナス"
|
||||||
},
|
},
|
||||||
"recency": {
|
"recency": {
|
||||||
"all_time": "全ての期間",
|
"all_time": "全ての期間",
|
||||||
|
|
@ -278,6 +279,19 @@
|
||||||
"loading": "ロード中...",
|
"loading": "ロード中...",
|
||||||
"not_found": "編成は見つかりませんでした"
|
"not_found": "編成は見つかりませんでした"
|
||||||
},
|
},
|
||||||
|
"page": {
|
||||||
|
"titles": {
|
||||||
|
"discover": "編成を見出す",
|
||||||
|
"profile": "@{{username}}さんの作った編成",
|
||||||
|
"team": "{{emoji}} {{teamName}}、{{username}}さんから / granblue.team",
|
||||||
|
"saved": "保存した編成"
|
||||||
|
},
|
||||||
|
"descriptions": {
|
||||||
|
"discover": "グランブルーファンタジーの編成をマルチ、属性、作った時間などで探したり保存したりできる",
|
||||||
|
"profile": "@{{username}}の編成を調査し、マルチ、属性、または作った時間でフィルターする",
|
||||||
|
"team": "granblue.teamで{{username}}さんが作った{{raidName}}の編成を調査できる"
|
||||||
|
}
|
||||||
|
},
|
||||||
"job_skills": {
|
"job_skills": {
|
||||||
"all": "全てのアビリティ",
|
"all": "全てのアビリティ",
|
||||||
"buffing": "強化アビリティ",
|
"buffing": "強化アビリティ",
|
||||||
|
|
@ -291,9 +305,10 @@
|
||||||
"no_skill": "設定されていません"
|
"no_skill": "設定されていません"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extra_weapons": "Additional<br/>Weapons",
|
"extra_weapons": "Additional Weapons",
|
||||||
"coming_soon": "開発中",
|
"coming_soon": "開発中",
|
||||||
"no_title": "無題",
|
"no_title": "無題",
|
||||||
"no_raid": "マルチなし",
|
"no_raid": "マルチなし",
|
||||||
"no_user": "無名"
|
"no_user": "無名",
|
||||||
|
"no_job": "ジョブなし"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,27 +33,27 @@ a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&.wind {
|
&.wind {
|
||||||
color: $wind-text-10;
|
color: var(--wind-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fire {
|
&.fire {
|
||||||
color: $fire-text-10;
|
color: var(--fire-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.water {
|
&.water {
|
||||||
color: $water-text-10;
|
color: var(--water-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.earth {
|
&.earth {
|
||||||
color: $earth-text-10;
|
color: var(--earth-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
color: $dark-text-10;
|
color: var(--dark-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.light {
|
&.light {
|
||||||
color: $light-text-10;
|
color: var(--light-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ h1,
|
||||||
h2,
|
h2,
|
||||||
h3,
|
h3,
|
||||||
p {
|
p {
|
||||||
color: $grey-15;
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
--card-bg: #{$grey-100};
|
--card-bg: #{$grey-100};
|
||||||
--bar-bg: #{$grey-100};
|
--bar-bg: #{$grey-100};
|
||||||
|
|
||||||
|
--link-text-hover: #{$text--link--hover--light};
|
||||||
|
|
||||||
// Light - Menus
|
// Light - Menus
|
||||||
--dialog-bg: #{$dialog--bg--light};
|
--dialog-bg: #{$dialog--bg--light};
|
||||||
|
|
||||||
|
|
@ -120,6 +122,8 @@
|
||||||
--card-bg: #{$page--element--bg--dark};
|
--card-bg: #{$page--element--bg--dark};
|
||||||
--bar-bg: #{$grey-10};
|
--bar-bg: #{$grey-10};
|
||||||
|
|
||||||
|
--link-text-hover: #{$text--link--hover--dark};
|
||||||
|
|
||||||
// Dark - Dialogs
|
// Dark - Dialogs
|
||||||
--dialog-bg: #{$dialog--bg--dark};
|
--dialog-bg: #{$dialog--bg--dark};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ $orange-10: #6b401b;
|
||||||
$orange-30: #825b39;
|
$orange-30: #825b39;
|
||||||
$orange-40: #925a2a;
|
$orange-40: #925a2a;
|
||||||
$orange-50: #a8703f;
|
$orange-50: #a8703f;
|
||||||
|
$orange-70: #d08f57;
|
||||||
$orange-80: #facea7;
|
$orange-80: #facea7;
|
||||||
$orange-90: #ffebd9;
|
$orange-90: #ffebd9;
|
||||||
|
|
||||||
|
|
@ -213,7 +214,7 @@ $subaura--orange--bg--dark: $orange-10;
|
||||||
$subaura--orange--card--bg--dark: $orange-30;
|
$subaura--orange--card--bg--dark: $orange-30;
|
||||||
$subaura--orange--primary--dark: $orange-00;
|
$subaura--orange--primary--dark: $orange-00;
|
||||||
$subaura--orange--secondary--dark: $orange-10;
|
$subaura--orange--secondary--dark: $orange-10;
|
||||||
$subaura--orange--text--dark: $orange-00;
|
$subaura--orange--text--dark: $orange-70;
|
||||||
|
|
||||||
// Color Definitions: Element Toggle
|
// Color Definitions: Element Toggle
|
||||||
$toggle--bg--light: $grey-90;
|
$toggle--bg--light: $grey-90;
|
||||||
|
|
@ -235,6 +236,9 @@ $text--tertiary--color--dark: $grey-50;
|
||||||
$text--tertiary--hover--light: $grey-40;
|
$text--tertiary--hover--light: $grey-40;
|
||||||
$text--tertiary--hover--dark: $grey-70;
|
$text--tertiary--hover--dark: $grey-70;
|
||||||
|
|
||||||
|
$text--link--hover--light: $grey-40;
|
||||||
|
$text--link--hover--dark: $grey-100;
|
||||||
|
|
||||||
// Color Definitions: Icon
|
// Color Definitions: Icon
|
||||||
$icon--secondary--color--light: $grey-70;
|
$icon--secondary--color--light: $grey-70;
|
||||||
$icon--secondary--color--dark: $grey-50;
|
$icon--secondary--color--dark: $grey-50;
|
||||||
|
|
|
||||||
2
types/WeaponSeries.d.ts
vendored
2
types/WeaponSeries.d.ts
vendored
|
|
@ -17,7 +17,7 @@ interface WeaponSeriesState {
|
||||||
astral: CheckedState
|
astral: CheckedState
|
||||||
epic: CheckedState
|
epic: CheckedState
|
||||||
ennead: CheckedState
|
ennead: CheckedState
|
||||||
cosmos: CheckedState
|
cosmic: CheckedState
|
||||||
ancestral: CheckedState
|
ancestral: CheckedState
|
||||||
superlative: CheckedState
|
superlative: CheckedState
|
||||||
vintage: CheckedState
|
vintage: CheckedState
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,8 @@ export const initialAppState: AppState = {
|
||||||
extra: false,
|
extra: false,
|
||||||
user: undefined,
|
user: undefined,
|
||||||
favorited: false,
|
favorited: false,
|
||||||
created_at: new Date().toISOString(),
|
created_at: '',
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: '',
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
weapons: {
|
weapons: {
|
||||||
|
|
|
||||||
|
|
@ -100,12 +100,16 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
||||||
id: 3,
|
id: 3,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
ultima: {
|
revenant: {
|
||||||
id: 17,
|
id: 4,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
bahamut: {
|
primal: {
|
||||||
id: 16,
|
id: 6,
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
|
beast: {
|
||||||
|
id: 7,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
regalia: {
|
regalia: {
|
||||||
|
|
@ -116,10 +120,6 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
||||||
id: 9,
|
id: 9,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
primal: {
|
|
||||||
id: 6,
|
|
||||||
checked: false,
|
|
||||||
},
|
|
||||||
olden_primal: {
|
olden_primal: {
|
||||||
id: 10,
|
id: 10,
|
||||||
checked: false,
|
checked: false,
|
||||||
|
|
@ -128,26 +128,30 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
||||||
id: 11,
|
id: 11,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
beast: {
|
hollowsky: {
|
||||||
id: 7,
|
id: 12,
|
||||||
checked: false,
|
|
||||||
},
|
|
||||||
rose: {
|
|
||||||
id: 15,
|
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
xeno: {
|
xeno: {
|
||||||
id: 13,
|
id: 13,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
hollowsky: {
|
|
||||||
id: 12,
|
|
||||||
checked: false,
|
|
||||||
},
|
|
||||||
astral: {
|
astral: {
|
||||||
id: 14,
|
id: 14,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
|
rose: {
|
||||||
|
id: 15,
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
|
bahamut: {
|
||||||
|
id: 16,
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
|
ultima: {
|
||||||
|
id: 17,
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
epic: {
|
epic: {
|
||||||
id: 18,
|
id: 18,
|
||||||
checked: false,
|
checked: false,
|
||||||
|
|
@ -156,7 +160,7 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
||||||
id: 19,
|
id: 19,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
cosmos: {
|
cosmic: {
|
||||||
id: 20,
|
id: 20,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
|
|
@ -176,6 +180,10 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
||||||
id: 24,
|
id: 24,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
|
proving: {
|
||||||
|
id: 25,
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
sephira: {
|
sephira: {
|
||||||
id: 28,
|
id: 28,
|
||||||
checked: false,
|
checked: false,
|
||||||
|
|
@ -184,6 +192,14 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
||||||
id: 29,
|
id: 29,
|
||||||
checked: false,
|
checked: false,
|
||||||
},
|
},
|
||||||
|
disaster: {
|
||||||
|
id: 30,
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
|
illustrious: {
|
||||||
|
id: 31,
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const emptyPaginationObject = {
|
export const emptyPaginationObject = {
|
||||||
|
|
|
||||||
|
|
@ -116,4 +116,8 @@ export const weaponSeries: WeaponSeries[] = [
|
||||||
id: 30,
|
id: 30,
|
||||||
slug: 'disaster',
|
slug: 'disaster',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 31,
|
||||||
|
slug: 'illustrious',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue