Merge pull request #11 from jedmund/team-details

Add details to teams
This commit is contained in:
Justin Edmund 2022-02-26 16:00:09 -08:00 committed by GitHub
commit aa20d0d4ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 609 additions and 29 deletions

View file

@ -3,6 +3,7 @@ import { useRouter } from 'next/router'
import { useCookies } from 'react-cookie'
import { useSnapshot } from 'valtio'
import clonedeep from 'lodash.clonedeep'
import * as Scroll from 'react-scroll'
import * as AlertDialog from '@radix-ui/react-alert-dialog'
@ -21,6 +22,7 @@ const BottomHeader = () => {
const app = useSnapshot(appState)
const router = useRouter()
const scroll = Scroll.animateScroll;
// Cookies
const [cookies] = useCookies(['user'])
@ -30,6 +32,15 @@ const BottomHeader = () => {
}
} : {}
function toggleDetails() {
appState.party.detailsVisible = !appState.party.detailsVisible
if (appState.party.detailsVisible)
scroll.scrollToBottom()
else
scroll.scrollToTop()
}
function deleteTeam(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
if (appState.party.editable && appState.party.id) {
api.endpoints.parties.destroy(appState.party.id, headers)
@ -53,9 +64,11 @@ const BottomHeader = () => {
}
const leftNav = () => {
return (
<Button icon="edit" click={() => {}}>Add more info</Button>
)
if (app.party.detailsVisible) {
return (<Button icon="edit" active={true} click={toggleDetails}>Hide info</Button>)
} else {
return (<Button icon="edit" click={toggleDetails}>Edit info</Button>)
}
}
const rightNav = () => {

View file

@ -61,6 +61,10 @@
}
}
&.Active {
background: white;
}
&.btn-blue {
background: $blue;
color: #8b8b8b;

View file

@ -14,6 +14,7 @@ import './index.scss'
import { ButtonType } from '~utils/enums'
interface Props {
active: boolean
disabled: boolean
icon: string | null
type: ButtonType
@ -26,6 +27,7 @@ interface State {
class Button extends React.Component<Props, State> {
static defaultProps: Props = {
active: false,
disabled: false,
icon: null,
type: ButtonType.Base,
@ -65,6 +67,7 @@ class Button extends React.Component<Props, State> {
const classes = classNames({
Button: true,
'Active': this.props.active,
'btn-pressed': this.state.isPressed,
'btn-disabled': this.props.disabled,
'destructive': this.props.type == ButtonType.Destructive

View file

@ -0,0 +1,29 @@
.Limited {
background: white;
border-radius: 6px;
border: 2px solid transparent;
box-sizing: border-box;
display: flex;
gap: $unit;
padding-right: $unit * 2;
&:focus-within {
border: 2px solid #275DC5;
box-shadow: 0 2px rgba(255, 255, 255, 1);
}
.Counter {
color: $grey-50;
font-weight: $bold;
line-height: 42px;
}
.Input {
background: transparent;
border-radius: 0;
&:focus {
outline: none;
}
}
}

View file

@ -0,0 +1,54 @@
import React, { useEffect, useState } from 'react'
import './index.scss'
interface Props {
fieldName: string
placeholder: string
value?: string
limit: number
error: string
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
}
const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(function fieldSet(props, ref) {
const fieldType = (['password', 'confirm_password'].includes(props.fieldName)) ? 'password' : 'text'
const [currentCount, setCurrentCount] = useState(0)
useEffect(() => {
setCurrentCount((props.value) ? props.limit - props.value.length : props.limit)
}, [props.limit, props.value])
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
setCurrentCount(props.limit - event.currentTarget.value.length)
if (props.onChange) props.onChange(event)
}
return (
<fieldset className="Fieldset">
<div className="Limited">
<input
autoComplete="off"
className="Input"
type={fieldType}
name={props.fieldName}
placeholder={props.placeholder}
defaultValue={props.value || ''}
onBlur={props.onBlur}
onChange={onChange}
maxLength={props.limit}
ref={ref}
formNoValidate
/>
<span className="Counter">{currentCount}</span>
</div>
{
props.error.length > 0 &&
<p className='InputError'>{props.error}</p>
}
</fieldset>
)
})
export default CharLimitedFieldset

View file

@ -4,6 +4,7 @@ import './index.scss'
interface Props {
fieldName: string
placeholder: string
value?: string
error: string
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
@ -17,9 +18,10 @@ const Fieldset = React.forwardRef<HTMLInputElement, Props>(function fieldSet(pro
<input
autoComplete="off"
className="Input"
name={props.fieldName}
type={fieldType}
name={props.fieldName}
placeholder={props.placeholder}
defaultValue={props.value || ''}
onBlur={props.onBlur}
onChange={props.onChange}
ref={ref}

View file

@ -1,4 +1,7 @@
.Extra {
#Party {
margin-bottom: $unit * 4;
}
#Party .Extra {
color: #888;
display: flex;
font-weight: 500;

View file

@ -2,8 +2,8 @@ import React, { useEffect, useState } from 'react'
import { useSnapshot } from 'valtio'
import { useCookies } from 'react-cookie'
import PartySegmentedControl from '~components/PartySegmentedControl'
import PartyDetails from '~components/PartyDetails'
import WeaponGrid from '~components/WeaponGrid'
import SummonGrid from '~components/SummonGrid'
import CharacterGrid from '~components/CharacterGrid'
@ -13,6 +13,7 @@ import { appState } from '~utils/appState'
import { GridType, TeamElement } from '~utils/enums'
import './index.scss'
import { AxiosResponse } from 'axios'
// Props
interface Props {
@ -33,29 +34,60 @@ const Party = (props: Props) => {
const { party } = useSnapshot(appState)
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon)
// Fetch data from the server
useEffect(() => {
const shortcode = (props.slug) ? props.slug : undefined
if (shortcode)
fetchDetails(shortcode)
else
appState.party.editable = true
}, [props.slug])
// Methods: Creating a new party
async function createParty(extra: boolean = false) {
let body = {
party: {
...(cookies.user) && { user_id: cookies.user.user_id },
is_extra: extra
extra: extra
}
}
return await api.endpoints.parties.create(body, headers)
}
// Methods: Updating the party's extra flag
// Methods: Updating the party's details
function checkboxChanged(event: React.ChangeEvent<HTMLInputElement>) {
appState.party.extra = event.target.checked
if (party.id) {
api.endpoints.parties.update(party.id, {
'party': { 'is_extra': event.target.checked }
'party': { 'extra': event.target.checked }
}, headers)
}
}
function updateDetails(name?: string, description?: string, raid?: Raid) {
if (appState.party.name !== name ||
appState.party.description !== description ||
appState.party.raid?.id !== raid?.id) {
if (appState.party.id)
api.endpoints.parties.update(appState.party.id, {
'party': {
'name': name,
'description': description,
'raid_id': raid?.id
}
}, headers)
.then(() => {
appState.party.name = name
appState.party.description = description
appState.party.raid = raid
})
}
}
// Methods: Navigating with segmented control
function segmentClicked(event: React.ChangeEvent<HTMLInputElement>) {
switch(event.target.value) {
@ -76,6 +108,33 @@ const Party = (props: Props) => {
}
}
// Methods: Fetch party details
function fetchDetails(shortcode: string) {
return api.endpoints.parties.getOne({ id: shortcode })
.then(response => processResult(response))
.catch(error => processError(error))
}
function processResult(response: AxiosResponse) {
appState.party.id = response.data.party.id
// Store the party's user-generated details
appState.party.name = response.data.party.name
appState.party.description = response.data.party.description
appState.party.raid = response.data.party.raid
}
function processError(error: any) {
if (error.response != null) {
if (error.response.status == 404) {
// setFound(false)
// setLoading(false)
}
} else {
console.error(error)
}
}
// Render: JSX components
const navigation = (
<PartySegmentedControl
@ -123,7 +182,13 @@ const Party = (props: Props) => {
return (
<div>
{ navigation }
{ currentGrid() }
<section id="Party">
{ currentGrid() }
</section>
{ <PartyDetails
editable={party.editable}
updateCallback={updateDetails}
/>}
</div>
)
}

View file

@ -0,0 +1,87 @@
.Details {
display: none; // This breaks transition, find a workaround
opacity: 0;
margin: 0 auto;
max-width: $unit * 95;
position: relative;
&.Editable {
top: $unit;
height: 0;
z-index: 2;
transition: opacity 0.2s ease-in-out,
top 0.2s ease-in-out;
&.Visible {
display: block;
height: auto;
margin-bottom: 40vh;
opacity: 1;
top: 0;
}
fieldset {
display: block;
width: 100%;
textarea {
min-height: $unit * 20;
width: 100%;
}
}
select {
appearance: none;
background-image: url('/icons/Arrow.svg');
background-repeat: no-repeat;
background-position-y: center;
background-position-x: 98%;
background-size: $unit * 1.5;
border: none;
border-radius: 6px;
color: $grey-00;
margin-bottom: $unit;
font-size: $font-regular;
padding: 0 ($unit * 2);
height: $unit * 6;
width: 100%;
&:hover {
cursor: pointer;
}
}
}
&.ReadOnly {
top: $unit * -1;
transition: opacity 0.2s ease-in-out,
top 0.2s ease-in-out;
&.Visible {
display: block;
height: auto;
opacity: 1;
top: 0;
}
h1 {
font-size: $font-xlarge;
font-weight: $normal;
text-align: left;
margin-bottom: $unit;
}
.Raid {
color: $grey-50;
font-size: $font-regular;
font-weight: $medium;
margin-bottom: $unit * 2;
}
p {
font-size: $font-regular;
line-height: $font-regular * 1.2;
}
}
}

View file

@ -0,0 +1,114 @@
import React, { useState } from 'react'
import { useSnapshot } from 'valtio'
import classNames from 'classnames'
import CharLimitedFieldset from '~components/CharLimitedFieldset'
import TextFieldset from '~components/TextFieldset'
import { appState } from '~utils/appState'
import './index.scss'
import RaidDropdown from '~components/RaidDropdown'
// Props
interface Props {
editable: boolean
updateCallback: (name?: string, description?: string, raid?: Raid) => void
}
const PartyDetails = (props: Props) => {
const appSnapshot = useSnapshot(appState)
const nameInput = React.createRef<HTMLInputElement>()
const descriptionInput = React.createRef<HTMLTextAreaElement>()
const raidSelect = React.createRef<HTMLSelectElement>()
const readOnlyClasses = classNames({
'Details': true,
'ReadOnly': true,
'Visible': !appSnapshot.party.detailsVisible
})
const editableClasses = classNames({
'Details': true,
'Editable': true,
'Visible': appSnapshot.party.detailsVisible
})
const [errors, setErrors] = useState<{ [key: string]: string }>({
name: '',
description: ''
})
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
event.preventDefault()
const { name, value } = event.target
let newErrors = errors
setErrors(newErrors)
}
function handleTextAreaChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
event.preventDefault()
const { name, value } = event.target
let newErrors = errors
setErrors(newErrors)
}
function updateDetails(event: React.ChangeEvent) {
const nameValue = nameInput.current?.value
const descriptionValue = descriptionInput.current?.value
const raid = appSnapshot.raids.find(raid => raid.id == raidSelect.current?.value)
props.updateCallback(nameValue, descriptionValue, raid)
}
const editable = (
<section className={editableClasses}>
<CharLimitedFieldset
fieldName="name"
placeholder="Name your team"
value={appSnapshot.party.name}
limit={50}
onBlur={updateDetails}
onChange={handleInputChange}
error={errors.name}
ref={nameInput}
/>
<RaidDropdown
selected={appSnapshot.party.raid?.id || ''}
onBlur={updateDetails}
ref={raidSelect}
/>
<TextFieldset
fieldName="name"
placeholder={"Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediels 1 first\nGood luck with RNG!"}
value={appSnapshot.party.description}
onBlur={updateDetails}
onChange={handleTextAreaChange}
error={errors.description}
ref={descriptionInput}
/>
</section>
)
const readOnly = (
<section className={readOnlyClasses}>
<h1>{ (appSnapshot.party.name) ? appSnapshot.party.name : 'No title' }</h1>
{ (appSnapshot.party.raid) ? <div className="Raid">{appSnapshot.party.raid.name.en}</div> : '' }
<p>{ (appSnapshot.party.description) ? appSnapshot.party.description : '' }</p>
</section>
)
return (
<div>
{readOnly}
{editable}
</div>
)
}
export default PartyDetails

View file

@ -3,6 +3,7 @@
gap: 58px;
justify-content: center;
margin: 0 auto;
margin-bottom: $unit * 3;
max-width: 760px;
position: relative;
}

View file

View file

@ -0,0 +1,93 @@
import React, { useEffect, useState } from 'react'
import { useCookies } from 'react-cookie'
import { appState } from '~utils/appState'
import api from '~utils/api'
import './index.scss'
// Props
interface Props {
selected?: string
onBlur: (event: React.ChangeEvent<HTMLSelectElement>) => void
}
const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function fieldSet(props, ref) {
const [cookies, _] = useCookies(['user'])
const headers = (cookies.user != null) ? {
headers: { 'Authorization': `Bearer ${cookies.user.access_token}` }
} : {}
const [raids, setRaids] = useState<Raid[][]>()
const [flatRaids, setFlatRaids] = useState<Raid[]>()
const raidGroups = [
'Assorted',
'Omega',
'T1 Summons',
'T2 Summons',
'Primarchs',
'Nightmare',
'Omega (Impossible)',
'Omega II',
'Tier 1 Summons (Impossible)',
'Tier 3 Summons',
'Ennead',
'Malice',
'6-Star Raids',
'Six-Dragons',
'Nightmare (Impossible)',
'Astral',
'Super Ultimate'
]
useEffect(() => {
fetchRaids()
}, [])
function fetchRaids() {
api.endpoints.raids.getAll(headers)
.then((response) => {
const raids = response.data.map((r: any) => r.raid)
appState.raids = raids
organizeRaids(raids)
})
}
function organizeRaids(raids: Raid[]) {
const numGroups = Math.max.apply(Math, raids.map(raid => raid.group))
let groupedRaids = []
for (let i = 0; i <= numGroups; i++) {
groupedRaids[i] = raids.filter(raid => raid.group == i)
}
setRaids(groupedRaids)
}
function raidGroup(index: number) {
const options = raids && raids.length > 0 && raids[index].length > 0 &&
raids[index].sort((a, b) => a.element - b.element).map((item, i) => {
return (
<option key={i} value={item.id}>{item.name.en}</option>
)
})
return (
<optgroup key={index} label={raidGroups[index]}>
{options}
</optgroup>
)
}
return (
<select key={props.selected} defaultValue={props.selected} onBlur={props.onBlur} ref={ref}>
{ Array.from(Array(raids?.length)).map((x, i) => {
return raidGroup(i)
})}
</select>
)
})
export default RaidDropdown

View file

@ -1,7 +1,6 @@
.SegmentedControlWrapper {
display: flex;
justify-content: center;
margin-bottom: $unit * 3;
}
.SegmentedControl {

View file

@ -57,7 +57,7 @@ const SignupModal = (props: Props) => {
setErrors(newErrors)
}, (error) => {
console.log(error)
console.error(error)
})
}
}
@ -88,7 +88,7 @@ const SignupModal = (props: Props) => {
props.close()
}, (error) => {
console.log(error)
console.error(error)
})
}
}

View file

@ -0,0 +1,5 @@
.Fieldset textarea {
color: $grey-00;
font-family: system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 21px;
}

View file

@ -0,0 +1,33 @@
import React from 'react'
import './index.scss'
interface Props {
fieldName: string
placeholder: string
value?: string
error: string
onBlur?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
}
const TextFieldset = React.forwardRef<HTMLTextAreaElement, Props>(function fieldSet(props, ref) {
return (
<fieldset className="Fieldset">
<textarea
className="Input"
name={props.fieldName}
placeholder={props.placeholder}
defaultValue={props.value || ''}
onBlur={props.onBlur}
onChange={props.onChange}
ref={ref}
/>
{
props.error.length > 0 &&
<p className='InputError'>{props.error}</p>
}
</fieldset>
)
})
export default TextFieldset

View file

@ -21,6 +21,10 @@
margin-bottom: $unit * 2;
margin-right: $unit * 2;
}
&:nth-last-child(-n+3) {
margin-bottom: 0;
}
}
.grid_weapons > *:nth-child(3n+3) {

View file

@ -70,6 +70,8 @@ const WeaponGrid = (props: Props) => {
}
function processResult(response: AxiosResponse) {
console.log("Retrieved data from server...")
// Store the response
const party = response.data.party
@ -252,7 +254,7 @@ const WeaponGrid = (props: Props) => {
const extraGridElement = (
<ExtraWeapons
grid={appState.grid.weapons.allWeapons}
grid={grid.weapons.allWeapons}
editable={party.editable}
offset={numWeapons}
updateObject={receiveWeaponFromSearch}

60
package-lock.json generated
View file

@ -24,6 +24,7 @@
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-i18next": "^11.15.3",
"react-scroll": "^1.8.5",
"sass": "^1.49.0",
"valtio": "^1.3.0"
},
@ -33,6 +34,7 @@
"@types/node": "17.0.11",
"@types/react": "17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-scroll": "^1.8.3",
"eslint": "8.7.0",
"eslint-config-next": "12.0.8",
"eslint-plugin-valtio": "^0.4.1",
@ -3093,6 +3095,15 @@
"@types/react": "*"
}
},
"node_modules/@types/react-scroll": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@types/react-scroll/-/react-scroll-1.8.3.tgz",
"integrity": "sha512-Xt0+Y58pwrIv+vRFxcyKDoo0gBWBR2eNMJ4XRMhQpZdGUtmjnszPi/wFEJSAY+5r83ypJsSA+hOpSc3hRpYlWw==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
@ -5330,6 +5341,11 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -5792,7 +5808,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@ -5802,8 +5817,7 @@
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/proxy-compare": {
"version": "2.0.2",
@ -5957,6 +5971,19 @@
}
}
},
"node_modules/react-scroll": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/react-scroll/-/react-scroll-1.8.5.tgz",
"integrity": "sha512-VcYFWDV2yGeuqeVCt3vxWTGWT4yCcefXOgvNZ16hSD0QTFzNNWiyZKWAVEgmz22PTKJlwRkspALaFI5+cr73OQ==",
"dependencies": {
"lodash.throttle": "^4.1.1",
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": "^15.5.4 || ^16.0.0 || ^17.0.0",
"react-dom": "^15.5.4 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/react-style-singleton": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.1.1.tgz",
@ -8943,6 +8970,15 @@
"@types/react": "*"
}
},
"@types/react-scroll": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@types/react-scroll/-/react-scroll-1.8.3.tgz",
"integrity": "sha512-Xt0+Y58pwrIv+vRFxcyKDoo0gBWBR2eNMJ4XRMhQpZdGUtmjnszPi/wFEJSAY+5r83ypJsSA+hOpSc3hRpYlWw==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
@ -10594,6 +10630,11 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -10918,7 +10959,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@ -10928,8 +10968,7 @@
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
@ -11020,6 +11059,15 @@
"tslib": "^1.0.0"
}
},
"react-scroll": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/react-scroll/-/react-scroll-1.8.5.tgz",
"integrity": "sha512-VcYFWDV2yGeuqeVCt3vxWTGWT4yCcefXOgvNZ16hSD0QTFzNNWiyZKWAVEgmz22PTKJlwRkspALaFI5+cr73OQ==",
"requires": {
"lodash.throttle": "^4.1.1",
"prop-types": "^15.7.2"
}
},
"react-style-singleton": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.1.1.tgz",

View file

@ -29,6 +29,7 @@
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-i18next": "^11.15.3",
"react-scroll": "^1.8.5",
"sass": "^1.49.0",
"valtio": "^1.3.0"
},
@ -38,6 +39,7 @@
"@types/node": "17.0.11",
"@types/react": "17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-scroll": "^1.8.3",
"eslint": "8.7.0",
"eslint-config-next": "12.0.8",
"eslint-plugin-valtio": "^0.4.1",

View file

@ -2,11 +2,9 @@ import React from 'react'
import { useRouter } from 'next/router'
import Party from '~components/Party'
import * as AlertDialog from '@radix-ui/react-alert-dialog'
const PartyRoute: React.FC = () => {
const router = useRouter()
const { party: slug } = router.query
const { party: slug } = useRouter().query
return (
<div id="Content">

View file

@ -1,3 +1,3 @@
<svg viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.3121 4.60937C2.09636 4.43701 1.78173 4.47219 1.60937 4.68793C1.43701 4.90368 1.47219 5.2183 1.68793 5.39066L6.68107 9.37966C6.75234 9.4366 6.8344 9.47089 6.91856 9.48351C7.05774 9.51055 7.20746 9.47854 7.32678 9.38296L12.311 5.39025C12.5265 5.21761 12.5613 4.90294 12.3886 4.68742C12.216 4.4719 11.9013 4.43714 11.6858 4.60979L7.00557 8.35897L2.3121 4.60937Z" >
<svg viewBox="0 0 14 14" fill="#444" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.3121 4.60937C2.09636 4.43701 1.78173 4.47219 1.60937 4.68793C1.43701 4.90368 1.47219 5.2183 1.68793 5.39066L6.68107 9.37966C6.75234 9.4366 6.8344 9.47089 6.91856 9.48351C7.05774 9.51055 7.20746 9.47854 7.32678 9.38296L12.311 5.39025C12.5265 5.21761 12.5613 4.90294 12.3886 4.68742C12.216 4.4719 11.9013 4.43714 11.6858 4.60979L7.00557 8.35897L2.3121 4.60937Z" />
</svg>

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 495 B

10
types/Raid.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
interface Raid {
id: string
name: {
en: string
jp: string
}
level: number
group: number
element: TeamElement
}

View file

@ -4,7 +4,7 @@ interface Entity {
name: string
}
type CollectionEndpoint = ({ query }: { query: AxiosRequestConfig }) => Promise<AxiosResponse<any>>
type CollectionEndpoint = (headers?: {}) => Promise<AxiosResponse<any>>
type IdEndpoint = ({ id }: { id: string }) => Promise<AxiosResponse<any>>
type IdWithObjectEndpoint = ({ id, object }: { id: string, object: string }) => Promise<AxiosResponse<any>>
type PostEndpoint = (object: {}, headers?: {}) => Promise<AxiosResponse<any>>
@ -41,7 +41,7 @@ class Api {
const resourceUrl = `${this.url}/${name}`
return {
getAll: ({ query }: { query: AxiosRequestConfig }) => axios.get(resourceUrl, { params: { query }}),
getAll: (headers?: {}) => axios.get(resourceUrl, headers),
getOne: ({ id }: { id: string }) => axios.get(`${resourceUrl}/${id}/`),
getOneWithObject: ({ id, object }: { id: string, object: string }) => axios.get(`${resourceUrl}/${id}/${object}`),
create: (object: {}, headers?: {}) => axios.post(resourceUrl, object, headers),
@ -88,5 +88,6 @@ api.createEntity( { name: 'parties' })
api.createEntity( { name: 'characters' })
api.createEntity( { name: 'weapons' })
api.createEntity( { name: 'summons' })
api.createEntity( { name: 'raids' })
export default api

View file

@ -6,6 +6,10 @@ interface AppState {
party: {
id: string | undefined,
editable: boolean,
detailsVisible: boolean,
name: string | undefined,
description: string | undefined,
raid: Raid | undefined,
element: number,
extra: boolean
},
@ -23,13 +27,18 @@ interface AppState {
},
search: {
sourceItem: GridCharacter | GridWeapon | GridSummon | undefined
}
},
raids: Raid[]
}
export const initialAppState: AppState = {
party: {
id: undefined,
editable: false,
detailsVisible: false,
name: undefined,
description: undefined,
raid: undefined,
element: 0,
extra: false
},
@ -47,7 +56,8 @@ export const initialAppState: AppState = {
},
search: {
sourceItem: undefined
}
},
raids: []
}
export const appState = proxy(initialAppState)