Implement rudimentary Bahamut Mode (#381)

Bahamut Mode lets me make sure people aren't doing naughty things behind
closed doors.
This commit is contained in:
Justin Edmund 2023-09-09 02:29:30 -07:00 committed by GitHub
parent 9bb5e721ff
commit ab4b563754
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 99 additions and 23 deletions

View file

@ -1,3 +1,14 @@
.bahamut {
background: #2b4683;
color: white;
text-align: center;
font-weight: $bold;
padding: $unit;
border-radius: $full-corner;
margin-bottom: $unit;
width: 100%;
}
.header { .header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View file

@ -172,6 +172,12 @@ const Header = () => {
gender={accountState.account.user.gender} gender={accountState.account.user.gender}
language={accountState.account.user.language} language={accountState.account.user.language}
theme={accountState.account.user.theme} theme={accountState.account.user.theme}
role={accountState.account.user.role}
bahamutMode={
accountState.account.user.role === 9
? accountState.account.user.bahamut
: false
}
onOpenChange={setSettingsModalOpen} onOpenChange={setSettingsModalOpen}
/> />
)} )}
@ -375,14 +381,19 @@ const Header = () => {
) )
return ( return (
<nav className={styles.header}> <>
{left} {accountState.account.user?.bahamut && (
{right} <div className={styles.bahamut}>Bahamut Mode is active</div>
{logoutConfirmationAlert} )}
{settingsModal} <nav className={styles.header}>
{loginModal} {left}
{signupModal} {right}
</nav> {logoutConfirmationAlert}
{settingsModal}
{loginModal}
{signupModal}
</nav>
</>
) )
} }

View file

@ -18,6 +18,7 @@ import { accountState } from '~utils/accountState'
import { pictureData } from '~utils/pictureData' import { pictureData } from '~utils/pictureData'
import styles from './index.module.scss' import styles from './index.module.scss'
import SwitchTableField from '~components/common/SwitchTableField'
interface Props { interface Props {
open: boolean open: boolean
@ -27,6 +28,8 @@ interface Props {
language?: string language?: string
theme?: string theme?: string
private?: boolean private?: boolean
role?: number
bahamutMode?: boolean
onOpenChange?: (open: boolean) => void onOpenChange?: (open: boolean) => void
} }
@ -53,6 +56,7 @@ const AccountModal = React.forwardRef<HTMLDivElement, Props>(
const [language, setLanguage] = useState(props.language || '') const [language, setLanguage] = useState(props.language || '')
const [gender, setGender] = useState(props.gender || 0) const [gender, setGender] = useState(props.gender || 0)
const [theme, setTheme] = useState(props.theme || 'system') const [theme, setTheme] = useState(props.theme || 'system')
const [bahamutMode, setBahamutMode] = useState(props.bahamutMode || false)
// Setup // Setup
const [pictureOpen, setPictureOpen] = useState(false) const [pictureOpen, setPictureOpen] = useState(false)
@ -135,6 +139,7 @@ const AccountModal = React.forwardRef<HTMLDivElement, Props>(
gender: user.gender, gender: user.gender,
language: user.language, language: user.language,
theme: user.theme, theme: user.theme,
bahamut: bahamutMode,
} }
const expiresAt = new Date() const expiresAt = new Date()
@ -145,6 +150,7 @@ const AccountModal = React.forwardRef<HTMLDivElement, Props>(
id: user.id, id: user.id,
username: user.username, username: user.username,
granblueId: '', granblueId: '',
role: user.role,
avatar: { avatar: {
picture: user.avatar.picture, picture: user.avatar.picture,
element: user.avatar.element, element: user.avatar.element,
@ -152,11 +158,13 @@ const AccountModal = React.forwardRef<HTMLDivElement, Props>(
language: user.language, language: user.language,
theme: user.theme, theme: user.theme,
gender: user.gender, gender: user.gender,
bahamut: bahamutMode,
} }
setOpen(false) setOpen(false)
if (props.onOpenChange) props.onOpenChange(false) if (props.onOpenChange) props.onOpenChange(false)
changeLanguage(router, user.language) changeLanguage(router, user.language)
if (props.bahamutMode != bahamutMode) router.reload()
}) })
} }
} }
@ -265,6 +273,15 @@ const AccountModal = React.forwardRef<HTMLDivElement, Props>(
</SelectTableField> </SelectTableField>
) )
const adminField = () => (
<SwitchTableField
name="admin"
label={t('modals.settings.labels.admin')}
value={props.bahamutMode}
onValueChange={(value: boolean) => setBahamutMode(value)}
/>
)
useEffect(() => { useEffect(() => {
setMounted(true) setMounted(true)
}, []) }, [])
@ -293,6 +310,7 @@ const AccountModal = React.forwardRef<HTMLDivElement, Props>(
{genderField()} {genderField()}
{languageField()} {languageField()}
{themeField()} {themeField()}
{props.role === 9 && adminField()}
</div> </div>
<DialogFooter <DialogFooter

View file

@ -17,6 +17,7 @@ import DialogFooter from '~components/common/DialogFooter'
import DialogContent from '~components/common/DialogContent' import DialogContent from '~components/common/DialogContent'
import styles from './index.module.scss' import styles from './index.module.scss'
import { userAgent } from 'next/server'
interface ErrorMap { interface ErrorMap {
[index: string]: string [index: string]: string
@ -140,6 +141,7 @@ const LoginModal = (props: Props) => {
const cookieObj: AccountCookie = { const cookieObj: AccountCookie = {
userId: resp.user.id, userId: resp.user.id,
username: resp.user.username, username: resp.user.username,
role: resp.user.role,
token: resp.access_token, token: resp.access_token,
} }
@ -169,6 +171,7 @@ const LoginModal = (props: Props) => {
language: user.language, language: user.language,
gender: user.gender, gender: user.gender,
theme: user.theme, theme: user.theme,
bahamut: false,
}, },
{ path: '/', expires: expiresAt } { path: '/', expires: expiresAt }
) )
@ -178,6 +181,7 @@ const LoginModal = (props: Props) => {
id: user.id, id: user.id,
username: user.username, username: user.username,
granblueId: '', granblueId: '',
role: user.role,
avatar: { avatar: {
picture: user.avatar.picture, picture: user.avatar.picture,
element: user.avatar.element, element: user.avatar.element,
@ -185,6 +189,7 @@ const LoginModal = (props: Props) => {
gender: user.gender, gender: user.gender,
language: user.language, language: user.language,
theme: user.theme, theme: user.theme,
bahamut: false,
} }
console.log('Authorizing account...') console.log('Authorizing account...')

View file

@ -97,6 +97,7 @@ const SignupModal = (props: Props) => {
const cookieObj: AccountCookie = { const cookieObj: AccountCookie = {
userId: resp.id, userId: resp.id,
username: resp.username, username: resp.username,
role: resp.role,
token: resp.token, token: resp.token,
} }
@ -130,6 +131,7 @@ const SignupModal = (props: Props) => {
language: user.language, language: user.language,
gender: user.gender, gender: user.gender,
theme: user.theme, theme: user.theme,
bahamut: false,
}, },
{ path: '/', expires: expiresAt } { path: '/', expires: expiresAt }
) )
@ -139,6 +141,7 @@ const SignupModal = (props: Props) => {
id: user.id, id: user.id,
username: user.username, username: user.username,
granblueId: '', granblueId: '',
role: user.role,
avatar: { avatar: {
picture: user.avatar.picture, picture: user.avatar.picture,
element: user.avatar.element, element: user.avatar.element,
@ -146,6 +149,7 @@ const SignupModal = (props: Props) => {
gender: user.gender, gender: user.gender,
language: user.language, language: user.language,
theme: user.theme, theme: user.theme,
bahamut: false,
} }
console.log('Authorizing account...') console.log('Authorizing account...')

View file

@ -65,11 +65,11 @@ function MyApp({ Component, pageProps }: AppProps) {
setHeaders() setHeaders()
if (cookieData.account && cookieData.account.token) { if (cookieData.account && cookieData.account.token) {
console.log(`Logged in as user "${cookieData.account.username}"`) console.log(`Logged in as user "${cookieData.account.username}"`)
accountState.account.authorized = true accountState.account.authorized = true
accountState.account.user = { accountState.account.user = {
id: cookieData.account.userId, id: cookieData.account.userId,
username: cookieData.account.username, username: cookieData.account.username,
role: cookieData.account.role,
granblueId: '', granblueId: '',
avatar: { avatar: {
picture: cookieData.user.avatar.picture, picture: cookieData.user.avatar.picture,
@ -78,6 +78,7 @@ function MyApp({ Component, pageProps }: AppProps) {
gender: cookieData.user.gender, gender: cookieData.user.gender,
language: cookieData.user.language, language: cookieData.user.language,
theme: cookieData.user.theme, theme: cookieData.user.theme,
bahamut: cookieData.user.bahamut,
} }
} else { } else {
console.log(`You are not currently logged in.`) console.log(`You are not currently logged in.`)
@ -101,6 +102,7 @@ function MyApp({ Component, pageProps }: AppProps) {
const cookieObj = { const cookieObj = {
userId: localUserId, userId: localUserId,
role: 1,
username: undefined, username: undefined,
token: undefined, token: undefined,
} }

View file

@ -120,7 +120,10 @@
"validation": { "validation": {
"guidebooks": "You cannot equip more than one of each Sephira Guidebook" "guidebooks": "You cannot equip more than one of each Sephira Guidebook"
}, },
"unauthorized": "You don't have permission to perform that action" "unauthorized": {
"title": "Unauthorized",
"description": "You don't have permission to perform that action"
}
}, },
"filters": { "filters": {
"name": "Filter", "name": "Filter",
@ -349,7 +352,8 @@
"language": "Language", "language": "Language",
"gender": "Main Character", "gender": "Main Character",
"private": "Private", "private": "Private",
"theme": "Theme" "theme": "Theme",
"admin": "Bahamut Mode"
}, },
"descriptions": { "descriptions": {
"picture": "Displayed next to your name", "picture": "Displayed next to your name",

View file

@ -120,7 +120,10 @@
"validation": { "validation": {
"guidebooks": "セフィラ導本を複数個装備することはできません" "guidebooks": "セフィラ導本を複数個装備することはできません"
}, },
"unauthorized": "行ったアクションを実行する権限がありません" "unauthorized": {
"title": "権限がありません",
"description": "行ったアクションを実行する権限がありません"
}
}, },
"filters": { "filters": {
"name": "フィルター", "name": "フィルター",
@ -348,7 +351,8 @@
"language": "言語", "language": "言語",
"gender": "主人公", "gender": "主人公",
"private": "プライベート", "private": "プライベート",
"theme": "表示" "theme": "表示",
"admin": "バハムートモード"
}, },
"descriptions": { "descriptions": {
"picture": "名前の隣に表示する", "picture": "名前の隣に表示する",

View file

@ -2,4 +2,5 @@ interface AccountCookie {
userId: string userId: string
username: string username: string
token: string token: string
role: number
} }

1
types/User.d.ts vendored
View file

@ -7,4 +7,5 @@ interface User {
element: string element: string
} }
gender: number gender: number
role: number
} }

View file

@ -4,6 +4,7 @@ export type UserState = {
id: string id: string
granblueId: string granblueId: string
username: string username: string
role: number
avatar: { avatar: {
picture: string picture: string
element: string element: string
@ -11,6 +12,7 @@ export type UserState = {
gender: number gender: number
language: string language: string
theme: string theme: string
bahamut: boolean
} }
interface AccountState { interface AccountState {

View file

@ -1,4 +1,4 @@
import { accountCookie } from './userToken' import { retrieveCookie } from './userToken'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { setCookie } from 'cookies-next' import { setCookie } from 'cookies-next'
import type { NextApiRequest, NextApiResponse } from 'next' import type { NextApiRequest, NextApiResponse } from 'next'
@ -8,7 +8,7 @@ export const createLocalId = (
res: NextApiResponse | undefined = undefined res: NextApiResponse | undefined = undefined
) => { ) => {
// If there is no account entry in cookies, create a UUID and store it // If there is no account entry in cookies, create a UUID and store it
if (!accountCookie(req, res)) { if (!retrieveCookie('account', req, res)) {
const uuid = uuidv4() const uuid = uuidv4()
const expiresAt = new Date() const expiresAt = new Date()
expiresAt.setDate(expiresAt.getDate() + 60) expiresAt.setDate(expiresAt.getDate() + 60)
@ -16,6 +16,7 @@ export const createLocalId = (
const cookieObj = { const cookieObj = {
userId: uuid, userId: uuid,
username: undefined, username: undefined,
role: 1,
token: undefined, token: undefined,
} }
@ -31,7 +32,7 @@ export const createLocalId = (
} }
export const getLocalId = () => { export const getLocalId = () => {
const cookie = accountCookie() const cookie = retrieveCookie('account')
if (cookie) { if (cookie) {
const parsed = JSON.parse(cookie as string) const parsed = JSON.parse(cookie as string)
if (parsed && !parsed.token) if (parsed && !parsed.token)

View file

@ -3,12 +3,13 @@ import ls, { get, set } from 'local-storage'
import { getCookie } from 'cookies-next' import { getCookie } from 'cookies-next'
import type { NextApiRequest, NextApiResponse } from 'next' import type { NextApiRequest, NextApiResponse } from 'next'
export const accountCookie = ( export const retrieveCookie = (
name: string,
req: NextApiRequest | undefined = undefined, req: NextApiRequest | undefined = undefined,
res: NextApiResponse | undefined = undefined res: NextApiResponse | undefined = undefined
) => { ) => {
const options = req && res ? { req, res } : {} const options = req && res ? { req, res } : {}
const cookie = getCookie('account', options) const cookie = getCookie(name, options)
return cookie ? cookie : undefined return cookie ? cookie : undefined
} }
@ -16,13 +17,24 @@ export const setHeaders = (
req: NextApiRequest | undefined = undefined, req: NextApiRequest | undefined = undefined,
res: NextApiResponse | undefined = undefined res: NextApiResponse | undefined = undefined
) => { ) => {
const cookie = accountCookie(req, res) const accountCookie = retrieveCookie('account', req, res)
if (cookie) { const userCookie = retrieveCookie('user', req, res)
const parsed = JSON.parse(cookie as string)
if (parsed.token) if (accountCookie && userCookie) {
axios.defaults.headers.common['Authorization'] = `Bearer ${parsed.token}` const account = JSON.parse(accountCookie as string)
const user = JSON.parse(userCookie as string)
if (account.token)
axios.defaults.headers.common['Authorization'] = `Bearer ${account.token}`
if (account.role === 9 && user.bahamut)
axios.defaults.headers.common['X-Admin-Mode'] = 'true'
else {
delete axios.defaults.headers.common['X-Admin-Mode']
}
} else { } else {
delete axios.defaults.headers.common['Authorization'] delete axios.defaults.headers.common['Authorization']
delete axios.defaults.headers.common['X-Admin-Mode']
} }
} }