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,