Added "about" modal to AdminX

refs. https://github.com/TryGhost/Product/issues/3949

Added an about modal to the new admin x settings.
---------

Co-authored-by: Ronald Langeveld <hi@ronaldlangeveld.com>
This commit is contained in:
Peter Zimon 2023-10-11 11:09:56 +02:00 committed by GitHub
parent 95528d25df
commit 586efd1f60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 189 additions and 8 deletions

View File

@ -10,6 +10,7 @@ import {GlobalDirtyStateProvider} from './hooks/useGlobalDirtyState';
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
import {ErrorBoundary as SentryErrorBoundary} from '@sentry/react';
import {Toaster} from 'react-hot-toast';
import {UpgradeStatusType} from './utils/globalTypes';
import {ZapierTemplate} from './components/settings/advanced/integrations/ZapierModal';
interface AppProps {
@ -24,6 +25,7 @@ interface AppProps {
onUpdate: (dataType: string, response: unknown) => void;
onInvalidate: (dataType: string) => void;
onDelete: (dataType: string, id: string) => void;
upgradeStatus?: UpgradeStatusType;
}
const queryClient = new QueryClient({
@ -38,7 +40,7 @@ const queryClient = new QueryClient({
}
});
function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate, darkMode = false, unsplashConfig, fetchKoenigLexical, sentryDSN, onUpdate, onInvalidate, onDelete}: AppProps) {
function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate, darkMode = false, unsplashConfig, fetchKoenigLexical, sentryDSN, onUpdate, onInvalidate, onDelete, upgradeStatus}: AppProps) {
const appClassName = clsx(
'admin-x-settings admin-x-base h-[100vh] w-full overflow-y-auto overflow-x-hidden',
darkMode && 'dark'
@ -47,7 +49,7 @@ function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate, d
return (
<SentryErrorBoundary>
<QueryClientProvider client={queryClient}>
<ServicesProvider fetchKoenigLexical={fetchKoenigLexical} ghostVersion={ghostVersion} officialThemes={officialThemes} sentryDSN={sentryDSN} unsplashConfig={unsplashConfig} zapierTemplates={zapierTemplates} onDelete={onDelete} onInvalidate={onInvalidate} onUpdate={onUpdate}>
<ServicesProvider fetchKoenigLexical={fetchKoenigLexical} ghostVersion={ghostVersion} officialThemes={officialThemes} sentryDSN={sentryDSN} unsplashConfig={unsplashConfig} upgradeStatus={upgradeStatus} zapierTemplates={zapierTemplates} onDelete={onDelete} onInvalidate={onInvalidate} onUpdate={onUpdate}>
<GlobalDataProvider>
<RoutingProvider externalNavigate={externalNavigate}>
<GlobalDirtyStateProvider>

View File

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="m7.152187499999999 4.21875 -6.0375000000000005 6.0365625000000005a1.40625 1.40625 0 0 0 0 1.9884375l6.0375000000000005 6.0375000000000005" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m15.347812499999998 4.21875 6.0375000000000005 6.0365625000000005a1.40625 1.40625 0 0 1 0 1.9884375l-6.0375000000000005 6.0375000000000005" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 608 B

View File

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M12.1875 21.474375a15.9271875 15.9271875 0 0 1 8.3025 -3.646875 1.5 1.5 0 0 0 1.3040625000000001 -1.4878125V2.2171875a1.5121875 1.5121875 0 0 0 -1.7203125 -1.5A16.009687500000002 16.009687500000002 0 0 0 12.1875 4.3125a1.53375 1.53375 0 0 1 -1.875 0A16.009687500000002 16.009687500000002 0 0 0 2.4234375 0.7190625 1.5121875 1.5121875 0 0 0 0.703125 2.2171875v14.1225a1.5 1.5 0 0 0 1.3040625000000001 1.4878125A15.9271875 15.9271875 0 0 1 10.3125 21.474375a1.5309375 1.5309375 0 0 0 1.875 0Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m11.25 4.629375 0 17.1665625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 851 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-0.75 -0.75 24 24" height="24" width="24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M8.4375 8.4375a2.8125 2.8125 0 1 1 3.75 2.6521875 1.40625 1.40625 0 0 0 -0.9375 1.3265625v0.943125" stroke-width="1.5"></path><path stroke="currentColor" d="M11.25 16.875a0.3515625 0.3515625 0 0 1 0 -0.703125" stroke-width="1.5"></path><path stroke="currentColor" d="M11.25 16.875a0.3515625 0.3515625 0 0 0 0 -0.703125" stroke-width="1.5"></path><path stroke="currentColor" stroke-miterlimit="10" d="M11.25 21.796875c5.8246875000000005 0 10.546875 -4.7221874999999995 10.546875 -10.546875S17.0746875 0.703125 11.25 0.703125 0.703125 5.4253124999999995 0.703125 11.25 5.4253124999999995 21.796875 11.25 21.796875Z" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 827 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 770 294" style="background-size:100% 100%;background-repeat:no-repeat;background-image:url(https://assets.ghost.io/admin/1597/assets/img/logos/ghost-logo-black-1-fb561a374422b405ec90fa586b05ebdf.png)" alt="Ghost"></svg>

After

Width:  |  Height:  |  Size: 237 B

View File

@ -6,7 +6,7 @@ interface SeparatorProps {
const Separator: React.FC<SeparatorProps> = ({className}) => {
if (!className) {
className = 'border-grey-200 dark:border-grey-600';
className = 'border-grey-200 dark:border-grey-800';
}
return <hr className={className} />;
};

View File

@ -15,10 +15,14 @@ export type Config = {
url: string,
version: string
}
enableDeveloperExperiments: boolean;
database: string;
blogUrl: string;
labs: Record<string, boolean>;
stripeDirect: boolean;
mail: string;
hostSettings?: {
siteId?: string;
limits?: {
// Partially typed, see https://github.com/TryGhost/SDK/tree/main/packages/limit-service
customIntegrations?: {

View File

@ -119,6 +119,10 @@ const Sidebar: React.FC = () => {
<SettingNavItem keywords={advancedSearchKeywords.labs} navid='labs' title="Labs" onClick={handleSectionClick} />
<SettingNavItem keywords={advancedSearchKeywords.history} navid='history' title="History" onClick={handleSectionClick} />
</SettingNavSection>
<Button className='mb-10 !font-normal' label='About Ghost' link onClick={() => {
updateRoute('about');
}} />
</div>
</div>
);

View File

@ -61,7 +61,8 @@ const modalPaths: {[key: string]: ModalName} = {
'recommendations/add': 'AddRecommendationModal',
'recommendations/edit': 'EditRecommendationModal',
'announcement-bar/edit': 'AnnouncementBarModal',
'embed-signup-form/show': 'EmbedSignupFormModal'
'embed-signup-form/show': 'EmbedSignupFormModal',
about: 'AboutModal'
};
function getHashPath(urlPath: string | undefined) {

View File

@ -1,6 +1,7 @@
import React, {createContext, useContext} from 'react';
import useSearchService, {SearchService} from '../../utils/search';
import {DefaultHeaderTypes} from '../../utils/unsplash/UnsplashTypes';
import {UpgradeStatusType} from '../../utils/globalTypes';
import {ZapierTemplate} from '../settings/advanced/integrations/ZapierModal';
export type ThemeVariant = {
@ -32,6 +33,7 @@ interface ServicesContextProps {
onInvalidate: (dataType: string) => void;
onDelete: (dataType: string, id: string) => void;
fetchKoenigLexical: FetchKoenigLexical;
upgradeStatus?: UpgradeStatusType;
}
interface ServicesProviderProps {
@ -45,6 +47,7 @@ interface ServicesProviderProps {
onInvalidate: (dataType: string) => void;
onDelete: (dataType: string, id: string) => void;
fetchKoenigLexical: FetchKoenigLexical;
upgradeStatus?: UpgradeStatusType;
}
const ServicesContext = createContext<ServicesContextProps>({
@ -63,10 +66,14 @@ const ServicesContext = createContext<ServicesContextProps>({
onUpdate: () => {},
onInvalidate: () => {},
onDelete: () => {},
fetchKoenigLexical: async () => {}
fetchKoenigLexical: async () => {},
upgradeStatus: {
isRequired: false,
message: ''
}
});
const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersion, zapierTemplates, officialThemes, unsplashConfig, sentryDSN, onUpdate, onInvalidate, onDelete, fetchKoenigLexical}) => {
const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersion, zapierTemplates, officialThemes, unsplashConfig, sentryDSN, onUpdate, onInvalidate, onDelete, fetchKoenigLexical, upgradeStatus}) => {
const search = useSearchService();
return (
@ -80,7 +87,8 @@ const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersi
onUpdate,
onInvalidate,
onDelete,
fetchKoenigLexical
fetchKoenigLexical,
upgradeStatus
}}>
{children}
</ServicesContext.Provider>
@ -96,3 +104,5 @@ export const useOfficialThemes = () => useServices().officialThemes;
export const useSearch = () => useServices().search;
export const useSentryDSN = () => useServices().sentryDSN;
export const useUpgradeStatus = () => useServices().upgradeStatus;

View File

@ -1,6 +1,7 @@
import type {NiceModalHocProps} from '@ebay/nice-modal-react';
import type {RoutingModalProps} from '../RoutingProvider';
import AboutModal from '../../settings/general/About';
import AddIntegrationModal from '../../settings/advanced/integrations/AddIntegrationModal';
import AddNewsletterModal from '../../settings/email/newsletters/AddNewsletterModal';
import AddRecommendationModal from '../../settings/site/recommendations/AddRecommendationModal';
@ -46,7 +47,8 @@ const modals = {
UserDetailModal,
ZapierModal,
AnnouncementBarModal,
EmbedSignupFormModal
EmbedSignupFormModal,
AboutModal
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} satisfies {[key: string]: ModalComponent<any>};

View File

@ -0,0 +1,141 @@
import Icon from '../../../admin-x-ds/global/Icon';
import Modal from '../../../admin-x-ds/global/modal/Modal';
import NiceModal from '@ebay/nice-modal-react';
import Separator from '../../../admin-x-ds/global/Separator';
import semverParse from 'semver/functions/parse';
import useRouting from '../../../hooks/useRouting';
import {ReactComponent as GhostLogo} from '../../../admin-x-ds/assets/images/ghost-logo.svg';
import {RoutingModalProps} from '../../providers/RoutingProvider';
import {useGlobalData} from '../../providers/GlobalDataProvider';
import {useUpgradeStatus} from '../../providers/ServiceProvider';
const AboutModal = NiceModal.create<RoutingModalProps>(({}) => {
const {updateRoute} = useRouting();
const globalData = useGlobalData();
let config = globalData.config;
const upgradeStatus = useUpgradeStatus();
function linkToGitHubReleases():string {
if (config.version.includes('-pre.')) {
try {
const semverVersion = semverParse(config.version, {includePrerelease: true} as any);
if (semverVersion && semverVersion.build?.[0]) {
return `https://github.com/TryGhost/Ghost/commit/${semverVersion.build[0]}`;
}
return '';
} catch (e) {
return '';
}
}
return `https://github.com/TryGhost/Ghost/releases/tag/v${config.version}`;
}
function copyrightYear():number {
const date = new Date();
return date.getFullYear();
}
function hasDeveloperExperiments():string {
if (config.enableDeveloperExperiments) {
return 'Enabled';
}
return 'Disabled';
}
function showSystemInfo() : boolean {
const isPro = !!config.hostSettings?.siteId;
if (isPro) {
return false;
}
return true;
}
function showDatabaseWarning() : boolean {
const isProduction = !!config.environment.match?.(/production/i);
const database = config.database;
// Show a warning if we're in production and not using MySQL 8
if (isProduction && database !== 'mysql8') {
return true;
}
// Show a warning if we're in development and using MySQL 5
if (!isProduction && database === 'mysql5') {
return true;
}
return false;
}
return (
<Modal
afterClose={() => {
updateRoute('');
}}
cancelLabel=''
footer={(<></>)}
size={540}
topRightContent='close'
>
<div className='flex flex-col gap-4 pb-7 text-sm'>
<GhostLogo className="h-auto w-[120px] dark:invert"/>
<div className='mt-3 flex flex-col gap-1.5'>
{
upgradeStatus?.message && (
<div className='gh-prose-links mb-4 rounded-sm border border-green p-5'>
<strong>Update available!</strong>
<div dangerouslySetInnerHTML={{__html: upgradeStatus.message}}/>
</div>
)
}
{
linkToGitHubReleases() && (
<div><strong>Version:</strong> <a className='text-green' href={linkToGitHubReleases()} rel="noopener noreferrer" target="_blank">{config.version}</a></div>
) || (
<div><strong>Version:</strong> {config.version}</div>
)
}
{
showSystemInfo() && (
<>
<div><strong>Environment:</strong> {config.environment}</div>
<div><strong>Database:</strong> {config.database}</div>
<div><strong>Mail:</strong> {config.mail ? config.mail : 'Native'}</div>
</>
)
}
{
hasDeveloperExperiments() && (
<div><strong>Developer experiments:</strong> {hasDeveloperExperiments()}</div>
)
}
{
showSystemInfo() && showDatabaseWarning() && (
<div className='text-red-500 dark:text-red-400'>
You are running an unsupported database in production. Please <a href="https://ghost.org/docs/faq/supported-databases/" rel="noopener noreferrer" target="_blank">upgrade to MySQL 8</a>.
</div>
)
}
</div>
<Separator />
<div className='flex flex-col gap-1.5'>
<a className='flex items-center gap-2 hover:text-grey-900 dark:hover:text-grey-400' href="https://ghost.org/docs/" rel="noopener noreferrer" target="_blank"><Icon name='book-open' size='sm' /> User documentation</a>
<a className='flex items-center gap-2 hover:text-grey-900 dark:hover:text-grey-400' href="https://forum.ghost.org/" rel="noopener noreferrer" target="_blank"><Icon name='question-circle' size='sm' /> Get help with Ghost</a>
<a className='flex items-center gap-2 hover:text-grey-900 dark:hover:text-grey-400' href="https://ghost.org/docs/contributing/" rel="noopener noreferrer" target="_blank"><Icon name='angle-brackets' size='sm' /> Get involved</a>
</div>
<Separator />
<p className='max-w-[460px] text-xs'>
Copyright © 2013 &ndash; {copyrightYear()} Ghost Foundation, released under the <a className='text-green' href="https://github.com/TryGhost/Ghost/blob/main/LICENSE" rel="noopener noreferrer" target="_blank">MIT license</a>. <a className='text-green' href="https://ghost.org/" rel="noopener noreferrer" target="_blank">Ghost</a> is a registered trademark of <a className='text-green' href="https://ghost.org/trademark/" rel="noopener noreferrer" target="_blank">Ghost Foundation Ltd</a>.
</p>
</div>
</Modal>
);
});
export default AboutModal;

View File

@ -79,3 +79,8 @@
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
/* Prose classes are for formatting arbitrary HTML that comes from the API */
.gh-prose-links a {
color: #30CF43;
}

View File

@ -0,0 +1,6 @@
// a rather miscellaneous collection of types - will remove in future
export type UpgradeStatusType = {
isRequired: boolean;
message: string;
}

View File

@ -298,6 +298,7 @@ export default class AdminXSettings extends Component {
@service router;
@service membersUtils;
@service themeManagement;
@service upgradeStatus;
@inject config;
@ -441,6 +442,7 @@ export default class AdminXSettings extends Component {
onUpdate={this.onUpdate}
onInvalidate={this.onInvalidate}
onDelete={this.onDelete}
upgradeStatus={this.upgradeStatus}
/>
</Suspense>
</ErrorHandler>