Merge pull request #52 from jedmund/bug-fixes

Bug fixes!
This commit is contained in:
Justin Edmund 2022-12-26 05:17:41 -08:00 committed by GitHub
commit 3f24886b98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 677 additions and 361 deletions

View file

@ -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">

View file

@ -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)}

View file

@ -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}
> >

View file

@ -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;

View file

@ -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}

View file

@ -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}
> >

View file

@ -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,

View file

@ -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>
) )
} }

View file

@ -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;

View file

@ -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);

View file

@ -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)

View file

@ -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;
}
} }

View file

@ -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>

View file

@ -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}

View file

@ -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 Fediels 1 first\nGood luck with RNG!' 'Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediels 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>
) )

View file

@ -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}
> >

View file

@ -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

View file

@ -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>

View file

@ -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}
> >

View file

@ -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;
}
} }

View file

@ -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">

View file

@ -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}

View file

@ -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'

View file

@ -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 {

View file

@ -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"
> >

View file

@ -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>
) )
} }

View file

@ -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()

View file

@ -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>
) )

View file

@ -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) {

View file

@ -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}&apos;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

View file

@ -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
}, },

View file

@ -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

View file

@ -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>

View file

@ -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"
} }

View file

@ -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": "ジョブなし"
} }

View file

@ -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 {

View file

@ -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};

View file

@ -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;

View file

@ -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

View file

@ -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: {

View file

@ -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 = {

View file

@ -116,4 +116,8 @@ export const weaponSeries: WeaponSeries[] = [
id: 30, id: 30,
slug: 'disaster', slug: 'disaster',
}, },
{
id: 31,
slug: 'illustrious',
},
] ]