diff --git a/src/api-def b/src/api-def index 93b8099e..7cfb7bcc 160000 --- a/src/api-def +++ b/src/api-def @@ -1 +1 @@ -Subproject commit 93b8099efe9119a39ece726348461d11b4ccede1 +Subproject commit 7cfb7bcc480bfa95286fecbb6cf8d654a17e0ef9 diff --git a/src/components/nav/const.tsx b/src/components/nav/const.tsx index d19dd107..83ad5c63 100644 --- a/src/components/nav/const.tsx +++ b/src/components/nav/const.tsx @@ -75,6 +75,12 @@ export const navItems: NavItems = [ {type: 'header', text: (t) => t.nav.header.others}, {type: 'path', path: GeneralPath.SPECIAL_THANKS, text: (t) => t.meta.inUse.thanks.title}, {type: 'path', path: GeneralPath.ABOUT, text: (t) => t.meta.inUse.about.title}, + { + type: 'path', + path: GeneralPath.ADMIN_ANNOUNCEMENT, + text: (t) => t.meta.inUse.admin.announcement.title, + adminOnly: true, + }, {type: 'divider'}, {type: 'path', path: GeneralPath.USER_SETTINGS, text: (t) => t.meta.inUse.user.settings.title}, {type: 'component', renderComponent: () => }, diff --git a/src/components/nav/elements/path.tsx b/src/components/nav/elements/path.tsx index a3744ba4..078c7fdf 100644 --- a/src/components/nav/elements/path.tsx +++ b/src/components/nav/elements/path.tsx @@ -13,7 +13,15 @@ import {NavItemPath} from '../type'; type Props = NavItemPath; export const NavPath = ({ - path, pathActiveBasis, href: hrefProps, text, disabled, onClick, pathnameNoLang, activeOverride, + path, + pathActiveBasis, + href: hrefProps, + text, + disabled, + onClick, + pathnameNoLang, + activeOverride, + adminOnly = false, }: Props) => { const {t, lang} = useI18n(); const href = !!path ? makeGeneralUrl(path, {lang}) : hrefProps; @@ -26,7 +34,7 @@ export const NavPath = ({ ); const props = { - className: `${isActive ? styles.active : ''} ${styles['nav-item']}`, + className: `${isActive ? styles.active : ''} ${adminOnly ? styles['nav-item-admin'] : styles['nav-item']}`, href, disabled, onClick, diff --git a/src/components/nav/main.module.css b/src/components/nav/main.module.css index d0c2af2e..affca3d6 100644 --- a/src/components/nav/main.module.css +++ b/src/components/nav/main.module.css @@ -55,6 +55,7 @@ div.nav-body button { div.nav-body div.text { border: 1px solid #656565; border-radius: 0.3rem; + background-image: var(--bs-gradient); padding: 0.5rem 1rem !important; background-color: #141414; text-align: center; @@ -62,8 +63,8 @@ div.nav-body div.text { div.nav-body a.nav-item { border: 1px solid #656565; border-radius: 0.3rem; - padding: 0.5rem 1rem !important; background-image: var(--bs-gradient); + padding: 0.5rem 1rem !important; } div.nav-body a.nav-item:not([class*=disabled]) { color: #f0f0f0; @@ -78,6 +79,25 @@ div.nav-body a.nav-item:hover, div.nav-body a.nav-item:focus, div.nav-body a.nav border: 1px solid #0855aa; background-color: #0855aa; } +div.nav-body a.nav-item-admin { + border: 1px solid #656565; + border-radius: 0.3rem; + background-image: var(--bs-gradient); + padding: 0.5rem 1rem !important; +} +div.nav-body a.nav-item-admin:not([class*=disabled]) { + color: #ff4457; +} +div.nav-body a.nav-item-admin.active { + color: #fff; + border: 1px solid #ff4457; + background-color: #ff4457; +} +div.nav-body a.nav-item-admin:hover, div.nav-body a.nav-item-admin:focus, div.nav-body a.nav-item-admin.nav-dropdown-hover { + color: #f0f0f0; + border: 1px solid #b30012; + background-color: #b30012; +} div.nav-body button { width: 100%; } diff --git a/src/components/nav/main.module.css.map b/src/components/nav/main.module.css.map index cdc35e21..1e6441cb 100644 --- a/src/components/nav/main.module.css.map +++ b/src/components/nav/main.module.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../../styles/utils.scss","main.module.scss","../../../styles/colors.scss"],"names":[],"mappings":"AAMA;EACE;EACA;EACA;;AAEA;EACE;;;ACPF;EAGE;EACA;EACA;EACA,cACE;EAKF;;AAEA;EACE;EACA;EACA,aAhBO;EAiBP,QAjBO;;AAqBP;EACE;;AAGF;EACE;;AAIJ;EACE;;;AAMJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EAWE;;AAEA;AAAA;EAEE;;AAGF;EAhBE;EACA;EAGA;EAeA,kBCxEQ;EDyER;;AAGF;EAvBE;EACA;EAGA;EAsBA;;AAEA;EACE,OC1EO;;AD6ET;EAGE,OCxFM;EDyFN;EACA,kBCnFO;;ADsFT;EAKE,OC1FO;ED2FP;EACA,kBAJa;;AAQjB;EAEE;;AAGF;EACE,kBC5GQ;ED6GR;;AAEA;EAEE;;AAEA;EACE;;AAMR;EACE,YACE;;AAOJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAKF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA","file":"main.module.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../../styles/utils.scss","main.module.scss","../../../styles/colors.scss"],"names":[],"mappings":"AAMA;EACE;EACA;EACA;;AAEA;EACE;;;ACPF;EAGE;EACA;EACA;EACA,cACE;EAKF;;AAEA;EACE;EACA;EACA,aAhBO;EAiBP,QAjBO;;AAqBP;EACE;;AAGF;EACE;;AAIJ;EACE;;;AAMJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EAYE;;AAEA;AAAA;EAEE;;AAGF;EAjBE;EACA;EACA;EAGA;EAeA,kBCzEQ;ED0ER;;AAGF;EAxBE;EACA;EACA;EAGA;;AAsBA;EACE,OCzEO;;AD4ET;EAGE,OCvFM;EDwFN;EACA,kBClFO;;ADqFT;EAKE,OCzFO;ED0FP;EACA,kBAJa;;AAQjB;EAlDE;EACA;EACA;EAGA;;AAgDA;EACE,OC9EO;;ADiFT;EAGE,OCxGO;EDyGP;EACA,kBCtFO;;ADyFT;EAKE,OCnHO;EDoHP;EACA,kBAJa;;AAQjB;EAEE;;AAGF;EACE,kBCrIQ;EDsIR;;AAEA;EAEE;;AAEA;EACE;;AAMR;EACE,YACE;;AAOJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAKF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA","file":"main.module.css"} \ No newline at end of file diff --git a/src/components/nav/main.module.scss b/src/components/nav/main.module.scss index 0e499576..522dc04f 100644 --- a/src/components/nav/main.module.scss +++ b/src/components/nav/main.module.scss @@ -62,6 +62,7 @@ div { @mixin nav-item() { border: 1px solid colors.$color-bw-101; border-radius: 0.3rem; + background-image: var(--bs-gradient); // !important to override the default style from BS `.navbar-nav .nav-link` padding: 0.5rem 1rem !important; @@ -86,8 +87,6 @@ div { a.nav-item { @include nav-item; - background-image: var(--bs-gradient); - &:not([class*="disabled"]) { color: colors.$color-bw-240; } @@ -111,6 +110,32 @@ div { } } + a.nav-item-admin { + @include nav-item; + + &:not([class*="disabled"]) { + color: colors.$color-danger; + } + + &.active { + $bg-active: colors.$color-danger; + + color: colors.$color-bw-255; + border: 1px solid $bg-active; + background-color: $bg-active; + } + + &:hover, + &:focus, + &.nav-dropdown-hover { + $bg-hovered: color.change(colors.$color-danger, $lightness: 35%); + + color: colors.$color-bw-240; + border: 1px solid $bg-hovered; + background-color: $bg-hovered; + } + } + button { // For all button component to fill the vertical space width: 100%; diff --git a/src/components/nav/main.test.tsx b/src/components/nav/main.test.tsx index 2d92effa..fdecd24e 100644 --- a/src/components/nav/main.test.tsx +++ b/src/components/nav/main.test.tsx @@ -34,6 +34,24 @@ describe('Navigation bar', () => { expect(aboutButton).toHaveAttribute('data-test-is-active', 'true'); }); + it('does not show admin only path for non-admin', async () => { + renderReact( + () => , + {hasSession: true, user: {isAdmin: false}}, + ); + + expect(screen.queryByText(translationEN.meta.inUse.admin.announcement.title)).not.toBeInTheDocument(); + }); + + it('shows admin only path', async () => { + renderReact( + () => , + {hasSession: true, user: {isAdmin: true}}, + ); + + expect(screen.getByText(translationEN.meta.inUse.admin.announcement.title)).toBeInTheDocument(); + }); + it('hides the navbar', async () => { const fnSetCollapse = jest.spyOn(layoutDispatchers, LayoutDispatcherName.CHANGE_COLLAPSE); diff --git a/src/components/nav/type.ts b/src/components/nav/type.ts index 9d345f95..ab552758 100644 --- a/src/components/nav/type.ts +++ b/src/components/nav/type.ts @@ -19,6 +19,7 @@ export type NavItemPath = NavItemCommon & { disabled?: boolean, onClick?: () => void, activeOverride?: boolean, + adminOnly?: boolean, } & ({ path?: GeneralPath, pathActiveBasis?: PagePath[], @@ -38,7 +39,11 @@ export type NavItemDivider = NavItemCommon & { type: 'divider', }; -export type NavItemDropdownContainable = NavItemHeader | NavItemPath | NavItemText | NavItemDivider; +export type NavItemDropdownContainable = + NavItemHeader | + NavItemPath | + NavItemText | + NavItemDivider; export type NavItemDropdownTitleProps = { open: boolean, diff --git a/src/i18n/translations/cht/translation.ts b/src/i18n/translations/cht/translation.ts index 5fdbdd0f..3771c1c9 100644 --- a/src/i18n/translations/cht/translation.ts +++ b/src/i18n/translations/cht/translation.ts @@ -774,6 +774,12 @@ export const translation: TranslationStruct = { description: '{{unitName}} 的角色故事全集。', }, }, + admin: { + announcement: { + title: '發送網站公告', + description: '發送網站公告的頁面。', + }, + }, user: { settings: { title: '使用者設定', diff --git a/src/i18n/translations/definition.ts b/src/i18n/translations/definition.ts index dbc7a47e..2cad8b67 100644 --- a/src/i18n/translations/definition.ts +++ b/src/i18n/translations/definition.ts @@ -634,6 +634,9 @@ export type TranslationStruct = { user: { settings: PageMetaTranslations, }, + admin: { + announcement: PageMetaTranslations, + }, }, error: { 401: PageMetaTranslations, diff --git a/src/i18n/translations/en/translation.ts b/src/i18n/translations/en/translation.ts index 950e1848..7503d9bd 100644 --- a/src/i18n/translations/en/translation.ts +++ b/src/i18n/translations/en/translation.ts @@ -816,6 +816,12 @@ export const translation: TranslationStruct = { description: 'All unit stories of {{unitName}}.', }, }, + admin: { + announcement: { + title: 'Website announcement', + description: 'Page to send the website announcement', + }, + }, user: { settings: { title: 'User settings', diff --git a/src/i18n/translations/jp/translation.ts b/src/i18n/translations/jp/translation.ts index e43f6fa5..a48e9aea 100644 --- a/src/i18n/translations/jp/translation.ts +++ b/src/i18n/translations/jp/translation.ts @@ -780,6 +780,12 @@ export const translation: TranslationStruct = { description: '全部の{{unitName}}のストーリー。', }, }, + admin: { + announcement: { + title: 'TBA', + description: 'TBA', + }, + }, user: { settings: { title: 'TBA', diff --git a/src/utils/meta/translations.ts b/src/utils/meta/translations.ts index f92b4cca..276b6fa5 100644 --- a/src/utils/meta/translations.ts +++ b/src/utils/meta/translations.ts @@ -36,6 +36,7 @@ export const metaTransFunctions: {[path in PagePath]: GetTranslationFunction t.meta.inUse.calc.enmity, [GeneralPath.GAME_DATAMINE_INDEX]: (t) => t.meta.inUse.gameData.datamine.index, [GeneralPath.USER_SETTINGS]: (t) => t.meta.inUse.user.settings, + [GeneralPath.ADMIN_ANNOUNCEMENT]: (t) => t.meta.inUse.admin.announcement, [AuthPath.SIGN_IN]: (t) => t.meta.inUse.auth.signIn, // Constructing paths [GeneralPath.SKILL_SUP]: (t) => t.meta.temp.constructing,