From 1f40cc0adf6b5232557839569402a4c02a1e1bdc Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:44:54 -0700 Subject: [PATCH 01/11] Add new button strings --- public/locales/en/common.json | 2 ++ public/locales/ja/common.json | 2 ++ 2 files changed, 4 insertions(+) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 4ba42722..3c49dd17 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -9,10 +9,12 @@ } }, "buttons": { + "cancel": "Cancel", "copy": "Copy link", "delete": "Delete team", "show_info": "Edit info", "hide_info": "Hide info", + "save_info": "Save info", "menu": "Menu", "new": "New", "wiki": "View more on gbf.wiki" diff --git a/public/locales/ja/common.json b/public/locales/ja/common.json index 2b5eb6ab..d533c5b5 100644 --- a/public/locales/ja/common.json +++ b/public/locales/ja/common.json @@ -9,9 +9,11 @@ } }, "buttons": { + "cancel": "キャンセルs", "copy": "リンクをコピー", "delete": "編成を削除", "show_info": "詳細を編集", + "save_info": "詳細を保存", "hide_info": "詳細を非表示", "menu": "メニュー", "new": "作成", From 19d0b14233498d77f4c59cc487f74160ff2079ff Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:45:04 -0700 Subject: [PATCH 02/11] Add timestamps to app state Party object --- utils/appState.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/utils/appState.tsx b/utils/appState.tsx index 0a4ed54d..d95d355c 100644 --- a/utils/appState.tsx +++ b/utils/appState.tsx @@ -13,7 +13,9 @@ interface AppState { element: number, extra: boolean, user: User | undefined, - favorited: boolean + favorited: boolean, + created_at: string + updated_at: string }, grid: { weapons: { @@ -48,7 +50,9 @@ export const initialAppState: AppState = { element: 0, extra: false, user: undefined, - favorited: false + favorited: false, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() }, grid: { weapons: { From 7b9cd245e347e1f751cfc555884469349a75cbfa Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:45:14 -0700 Subject: [PATCH 03/11] Add element-colored links --- styles/globals.scss | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/styles/globals.scss b/styles/globals.scss index 23837dbf..3f878f97 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -28,6 +28,30 @@ main { a { text-decoration: none; + + &.wind { + color: $wind-text-dark; + } + + &.fire { + color: $fire-text-dark; + } + + &.water { + color: $water-text-dark; + } + + &.earth { + color: $earth-text-dark; + } + + &.dark { + color: $dark-text-dark; + } + + &.light { + color: $light-text-dark; + } } button, input { From 844983796a9653f7bcc40ce7bc8298d7ae1114fe Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:45:18 -0700 Subject: [PATCH 04/11] Create LargeCheck.svg --- public/icons/LargeCheck.svg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 public/icons/LargeCheck.svg diff --git a/public/icons/LargeCheck.svg b/public/icons/LargeCheck.svg new file mode 100644 index 00000000..63a4e2a0 --- /dev/null +++ b/public/icons/LargeCheck.svg @@ -0,0 +1,3 @@ + + + From 258f9cf89900f57cd61939ffb835eba7dec35e39 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:45:49 -0700 Subject: [PATCH 05/11] Persist timestamp state in grid API calls --- components/CharacterGrid/index.tsx | 4 +++- components/SummonGrid/index.tsx | 4 +++- components/WeaponGrid/index.tsx | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/components/CharacterGrid/index.tsx b/components/CharacterGrid/index.tsx index 88dbaf50..10daad5c 100644 --- a/components/CharacterGrid/index.tsx +++ b/components/CharacterGrid/index.tsx @@ -86,7 +86,9 @@ const CharacterGrid = (props: Props) => { appState.party.id = party.id appState.party.user = party.user appState.party.favorited = party.favorited - + appState.party.created_at = party.created_at + appState.party.updated_at = party.updated_at + setFound(true) setLoading(false) diff --git a/components/SummonGrid/index.tsx b/components/SummonGrid/index.tsx index 18ce4398..f1ee104b 100644 --- a/components/SummonGrid/index.tsx +++ b/components/SummonGrid/index.tsx @@ -99,7 +99,9 @@ const SummonGrid = (props: Props) => { appState.party.id = party.id appState.party.user = party.user appState.party.favorited = party.favorited - + appState.party.created_at = party.created_at + appState.party.updated_at = party.updated_at + setFound(true) setLoading(false) diff --git a/components/WeaponGrid/index.tsx b/components/WeaponGrid/index.tsx index 7bcba0af..3eb6b2a3 100644 --- a/components/WeaponGrid/index.tsx +++ b/components/WeaponGrid/index.tsx @@ -93,6 +93,8 @@ const WeaponGrid = (props: Props) => { appState.party.extra = party.extra appState.party.user = party.user appState.party.favorited = party.favorited + appState.party.created_at = party.created_at + appState.party.updated_at = party.updated_at setFound(true) setLoading(false) From 76a9411508a5a2275cf68d701d982dc7d8d19854 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:46:23 -0700 Subject: [PATCH 06/11] Add CheckIcon --- components/Button/index.scss | 6 ++++++ components/Button/index.tsx | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/components/Button/index.scss b/components/Button/index.scss index 9c60cd60..f9053487 100644 --- a/components/Button/index.scss +++ b/components/Button/index.scss @@ -82,6 +82,12 @@ width: 12px; } + &.check svg { + margin-top: 1px; + height: 14px; + width: auto; + } + &.stroke svg { fill: none; stroke: $grey-50; diff --git a/components/Button/index.tsx b/components/Button/index.tsx index 3476e473..f83f5e76 100644 --- a/components/Button/index.tsx +++ b/components/Button/index.tsx @@ -4,6 +4,7 @@ import classNames from 'classnames' import Link from 'next/link' import AddIcon from '~public/icons/Add.svg' +import CheckIcon from '~public/icons/LargeCheck.svg' import CrossIcon from '~public/icons/Cross.svg' import EditIcon from '~public/icons/Edit.svg' import LinkIcon from '~public/icons/Link.svg' @@ -65,6 +66,12 @@ const Button = (props: Props) => { ) + const checkIcon = ( + + + + ) + const crossIcon = ( @@ -96,6 +103,7 @@ const Button = (props: Props) => { case 'new': icon = addIcon; break case 'menu': icon = menuIcon; break case 'link': icon = linkIcon; break + case 'check': icon = checkIcon; break case 'cross': icon = crossIcon; break case 'edit': icon = editIcon; break case 'save': icon = saveIcon; break From 7067db6f2c84ab0640e89e8bdb4590aa5acc496a Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:46:33 -0700 Subject: [PATCH 07/11] Fix spacing between CharacterUnit and PartyDetails --- components/CharacterUnit/index.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/CharacterUnit/index.scss b/components/CharacterUnit/index.scss index 4de56900..b51bd057 100644 --- a/components/CharacterUnit/index.scss +++ b/components/CharacterUnit/index.scss @@ -2,7 +2,9 @@ display: flex; flex-direction: column; gap: calc($unit / 2); + min-height: 320px; max-width: 200px; + margin-bottom: $unit * 4; &.editable .CharacterImage:hover { border: $hover-stroke; From 83bb782476028538c4a97d1354e89877c5fd6e84 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:46:43 -0700 Subject: [PATCH 08/11] Remove extra spacing below Party --- components/Party/index.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/Party/index.scss b/components/Party/index.scss index 77ab6f2b..47c0c0f6 100644 --- a/components/Party/index.scss +++ b/components/Party/index.scss @@ -1,6 +1,3 @@ -#Party { - margin-bottom: $unit * 4; -} #Party .Extra { color: #888; display: flex; From 9291e5501a396f992f332b93bcc72324c12447a5 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:46:58 -0700 Subject: [PATCH 09/11] Add deleteTeam method --- components/Party/index.tsx | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/components/Party/index.tsx b/components/Party/index.tsx index 59332b86..2dfb7ace 100644 --- a/components/Party/index.tsx +++ b/components/Party/index.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useRouter } from 'next/router' import { useSnapshot } from 'valtio' import { useCookies } from 'react-cookie' import clonedeep from 'lodash.clonedeep' @@ -32,6 +33,9 @@ const Party = (props: Props) => { } : {} }, [cookies.account]) + // Set up router + const router = useRouter() + // Set up states const { party } = useSnapshot(appState) const [currentTab, setCurrentTab] = useState(GridType.Weapon) @@ -81,10 +85,34 @@ const Party = (props: Props) => { appState.party.name = name appState.party.description = description appState.party.raid = raid + appState.party.updated_at = party.updated_at }) } } + // Deleting the party + function deleteTeam(event: React.MouseEvent) { + if (appState.party.editable && appState.party.id) { + api.endpoints.parties.destroy({ id: appState.party.id, params: headers }) + .then(() => { + // Push to route + router.push('/') + + // Clean state + const resetState = clonedeep(initialAppState) + Object.keys(resetState).forEach((key) => { + appState[key] = resetState[key] + }) + + // Set party to be editable + appState.party.editable = true + }) + .catch((error) => { + console.error(error) + }) + } + } + // Methods: Navigating with segmented control function segmentClicked(event: React.ChangeEvent) { switch(event.target.value) { @@ -110,6 +138,8 @@ const Party = (props: Props) => { appState.party.id = response.data.party.id appState.party.user = response.data.party.user appState.party.favorited = response.data.party.favorited + appState.party.created_at = response.data.party.created_at + appState.party.updated_at = response.data.party.updated_at // Store the party's user-generated details appState.party.name = response.data.party.name @@ -194,6 +224,7 @@ const Party = (props: Props) => { { } ) From bda2639d88b5d5f675861eb8bfba9f4eb81642d9 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:47:13 -0700 Subject: [PATCH 10/11] Redesign read only state and add save button --- components/PartyDetails/index.scss | 117 +++++++++++++++--- components/PartyDetails/index.tsx | 191 +++++++++++++++++++++++++++-- 2 files changed, 280 insertions(+), 28 deletions(-) diff --git a/components/PartyDetails/index.scss b/components/PartyDetails/index.scss index abe4e39a..50f88ffb 100644 --- a/components/PartyDetails/index.scss +++ b/components/PartyDetails/index.scss @@ -31,6 +31,22 @@ width: 100%; } } + + .bottom { + display: flex; + flex-direction: row; + gap: $unit; + + .left { + flex-grow: 1; + } + + .right { + display: flex; + flex-direction: row; + gap: $unit; + } + } } &.ReadOnly { @@ -45,26 +61,8 @@ top: 0; } - h1 { - font-size: $font-xlarge; - font-weight: $normal; - text-align: left; - margin-bottom: $unit; - } - - a { - color: $blue; - - &:hover { - text-decoration: underline; - } - } - - .Raid { - color: $grey-50; - font-size: $font-regular; - font-weight: $medium; - margin-bottom: $unit * 2; + a:hover { + text-decoration: underline; } p { @@ -72,5 +70,84 @@ line-height: $font-regular * 1.2; white-space: pre-line; } + + + h1 { + font-size: $font-xlarge; + font-weight: $normal; + text-align: left; + margin-bottom: $unit; + } + + .info { + align-items: center; + display: flex; + flex-direction: row; + gap: $unit; + margin-bottom: $unit * 2; + + .left { + flex-grow: 1; + } + } + + .attribution { + align-items: center; + display: flex; + flex-direction: row; + + & > div { + align-items: center; + display: inline-flex; + font-size: $font-small; + height: 26px; + } + + time { + font-size: $font-small; + } + + & > *:not(:last-child):after { + content: " · "; + margin: 0 calc($unit / 2); + } + } + + .user { + align-items: center; + display: inline-flex; + gap: calc($unit / 2); + margin-top: 1px; + + img, .no-user { + $diameter: 24px; + + border-radius: calc($diameter / 2); + height: $diameter; + width: $diameter; + } + + img.gran { + background-color: #CEE7FE; + } + + img.djeeta { + background-color: #FFE1FE; + } + + .no-user { + background: $grey-80; + } + } + } +} + +.EmptyDetails { + display: none; + justify-content: center; + margin-bottom: $unit * 10; + + &.Visible { + display: flex; } } \ No newline at end of file diff --git a/components/PartyDetails/index.tsx b/components/PartyDetails/index.tsx index bc765ca7..4c24d3d9 100644 --- a/components/PartyDetails/index.tsx +++ b/components/PartyDetails/index.tsx @@ -1,27 +1,53 @@ import React, { useState } from 'react' import Head from 'next/head' -import Router, { useRouter } from 'next/router' +import { useRouter } from 'next/router' import { useSnapshot } from 'valtio' +import { useTranslation } from 'next-i18next' + import Linkify from 'react-linkify' import classNames from 'classnames' +import * as AlertDialog from '@radix-ui/react-alert-dialog' +import CrossIcon from '~public/icons/Cross.svg' + +import Button from '~components/Button' import CharLimitedFieldset from '~components/CharLimitedFieldset' import RaidDropdown from '~components/RaidDropdown' import TextFieldset from '~components/TextFieldset' +import { accountState } from '~utils/accountState' import { appState } from '~utils/appState' import './index.scss' +import Link from 'next/link' +import { formatTimeAgo } from '~utils/timeAgo' + +const emptyRaid: Raid = { + id: '', + name: { + en: '', + ja: '' + }, + slug: '', + level: 0, + group: 0, + element: 0 +} // Props interface Props { editable: boolean updateCallback: (name?: string, description?: string, raid?: Raid) => void + deleteCallback: (event: React.MouseEvent) => void } const PartyDetails = (props: Props) => { const { party, raids } = useSnapshot(appState) + const { account } = useSnapshot(accountState) + + const { t } = useTranslation('common') const router = useRouter() + const locale = router.locale || 'en' const nameInput = React.createRef() const descriptionInput = React.createRef() @@ -39,6 +65,25 @@ const PartyDetails = (props: Props) => { 'Visible': party.detailsVisible }) + const emptyClasses = classNames({ + 'EmptyDetails': true, + 'Visible': !party.detailsVisible + }) + + const userClass = classNames({ + 'user': true, + 'empty': !party.user + }) + + const linkClass = classNames({ + 'wind': party && party.element == 1, + 'fire': party && party.element == 2, + 'water': party && party.element == 3, + 'earth': party && party.element == 4, + 'dark': party && party.element == 5, + 'light': party && party.element == 6 + }) + const [errors, setErrors] = useState<{ [key: string]: string }>({ name: '', description: '' @@ -62,12 +107,100 @@ const PartyDetails = (props: Props) => { setErrors(newErrors) } - function updateDetails(event: React.ChangeEvent) { + function toggleDetails() { + appState.party.detailsVisible = !appState.party.detailsVisible + + // if (appState.party.detailsVisible) + // scroll.scrollToBottom() + // else + // scroll.scrollToTop() + } + + function updateDetails(event: React.MouseEvent) { const nameValue = nameInput.current?.value const descriptionValue = descriptionInput.current?.value const raid = raids.find(raid => raid.slug === raidSelect.current?.value) props.updateCallback(nameValue, descriptionValue, raid) + toggleDetails() + } + + const userImage = () => { + if (party.user) + return ( + {party.user.picture.picture} + ) + else + return (
) + } + + const userBlock = () => { + return ( +
+ { userImage() } + { (party.user) ? party.user.username : t('no_user') } +
+ ) + } + + const linkedUserBlock = (user: User) => { + return ( +
+ + {userBlock()} + +
+ ) + } + + const linkedRaidBlock = (raid: Raid) => { + return ( + + ) + } + + const deleteButton = () => { + if (party.editable) { + return ( + + + + + + {t('buttons.delete')} + + + + + + {t('modals.delete_team.title')} + + + {t('modals.delete_team.description')} + +
+ {t('modals.delete_team.buttons.cancel')} + props.deleteCallback(e)}>{t('modals.delete_team.buttons.confirm')} +
+
+
+
+ ) + } else { + return ('') + } } const editable = ( @@ -77,7 +210,6 @@ const PartyDetails = (props: Props) => { placeholder="Name your team" value={party.name} limit={50} - onBlur={updateDetails} onChange={handleInputChange} error={errors.name} ref={nameInput} @@ -85,29 +217,72 @@ const PartyDetails = (props: Props) => { + +
+
+ { (router.pathname !== '/new') ? deleteButton() : '' } +
+
+ + + +
+
) const readOnly = (
- { (party.name) ?

{party.name}

: '' } - { (party.raid) ?
{party.raid.name.en}
: '' } +
+
+ { (party.name) ?

{party.name}

: '' } +
+ { (party.user) ? linkedUserBlock(party.user) : userBlock() } + { (party.raid) ? linkedRaidBlock(party.raid) : '' } + { (party.created_at != undefined) + ? + : '' } +
+
+
+ { (party.editable) + ? + :
} +
+
{ (party.description) ?

{party.description}

: '' }
) + const emptyDetails = ( +
+ +
+ ) + const generateTitle = () => { let title = '' @@ -138,7 +313,7 @@ const PartyDetails = (props: Props) => { - {readOnly} + { (editable && (party.name || party.description || party.raid)) ? readOnly : emptyDetails} {editable}
) From 5048433e53e92d72c9d63bc3bade9124878f25eb Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 14 Mar 2022 16:47:18 -0700 Subject: [PATCH 11/11] Cleanup --- components/BottomHeader/index.tsx | 121 ------------------------------ components/Layout/index.tsx | 2 - types/WeaponGridProps.d.ts | 6 -- 3 files changed, 129 deletions(-) delete mode 100644 components/BottomHeader/index.tsx delete mode 100644 types/WeaponGridProps.d.ts diff --git a/components/BottomHeader/index.tsx b/components/BottomHeader/index.tsx deleted file mode 100644 index abcbd7ad..00000000 --- a/components/BottomHeader/index.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import React from 'react' -import { useRouter } from 'next/router' -import { useCookies } from 'react-cookie' -import { useSnapshot } from 'valtio' -import { useTranslation } from 'next-i18next' - -import clonedeep from 'lodash.clonedeep' -import * as Scroll from 'react-scroll' - -import * as AlertDialog from '@radix-ui/react-alert-dialog' - -import Header from '~components/Header' -import Button from '~components/Button' - -import api from '~utils/api' -import { appState, initialAppState } from '~utils/appState' - -import CrossIcon from '~public/icons/Cross.svg' - -const BottomHeader = () => { - const { t } = useTranslation('common') - - const app = useSnapshot(appState) - - const router = useRouter() - const scroll = Scroll.animateScroll; - - // Cookies - const [cookies] = useCookies(['account']) - const headers = (cookies.account != null) ? { - headers: { - 'Authorization': `Bearer ${cookies.account.access_token}` - } - } : {} - - function toggleDetails() { - appState.party.detailsVisible = !appState.party.detailsVisible - - if (appState.party.detailsVisible) - scroll.scrollToBottom() - else - scroll.scrollToTop() - } - - function deleteTeam(event: React.MouseEvent) { - if (appState.party.editable && appState.party.id) { - api.endpoints.parties.destroy({ id: appState.party.id, params: headers }) - .then(() => { - // Push to route - router.push('/') - - // Clean state - const resetState = clonedeep(initialAppState) - Object.keys(resetState).forEach((key) => { - appState[key] = resetState[key] - }) - - // Set party to be editable - appState.party.editable = true - }) - .catch((error) => { - console.error(error) - }) - } - } - - const leftNav = () => { - if (router.pathname === '/p/[party]' || router.pathname === '/new') { - if (app.party.detailsVisible) { - return () - } else { - return () - } - } else { - return (
) - } - } - - const rightNav = () => { - if (app.party.editable && router.route === '/p/[party]') { - return ( - - - - - - {t('buttons.delete')} - - - - - - {t('modals.delete_team.title')} - - - {t('modals.delete_team.description')} - -
- {t('modals.delete_team.buttons.cancel')} - deleteTeam(e)}>{t('modals.delete_team.buttons.confirm')} -
-
-
-
- ) - } else { - return (
) - } - } - - - return ( -
- ) -} - -export default BottomHeader diff --git a/components/Layout/index.tsx b/components/Layout/index.tsx index f939c35a..5d03fb7c 100644 --- a/components/Layout/index.tsx +++ b/components/Layout/index.tsx @@ -1,6 +1,5 @@ import type { ReactElement } from 'react' import TopHeader from '~components/TopHeader' -import BottomHeader from '~components/BottomHeader' interface Props { children: ReactElement @@ -11,7 +10,6 @@ const Layout = ({children}: Props) => { <>
{children}
- ) } diff --git a/types/WeaponGridProps.d.ts b/types/WeaponGridProps.d.ts deleted file mode 100644 index b38ed193..00000000 --- a/types/WeaponGridProps.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -interface WeaponGridProps { - onReceiveData: (Weapon, number) => void - weapon: Weapon | undefined - position: number - editable: boolean -} \ No newline at end of file