diff --git a/.github/scripts/dev.js b/.github/scripts/dev.js index 5ff1f1bcbd..f6ffbe2882 100644 --- a/.github/scripts/dev.js +++ b/.github/scripts/dev.js @@ -51,8 +51,8 @@ const COMMAND_TYPESCRIPT = { }; const COMMANDS_ADMINX = [{ - name: 'adminXDS', - command: 'nx watch --projects=apps/admin-x-design-system -- nx run \\$NX_PROJECT_NAME:build --skip-nx-cache', + name: 'adminXDeps', + command: 'nx watch --projects=apps/admin-x-design-system,apps/admin-x-framework -- nx run \\$NX_PROJECT_NAME:build --skip-nx-cache', cwd: path.resolve(__dirname, '../..'), prefixColor: '#C35831', env: {} diff --git a/apps/admin-x-design-system/src/global/form/CodeEditor.tsx b/apps/admin-x-design-system/src/global/form/CodeEditor.tsx index 376a2bd1ea..f429b62d9b 100644 --- a/apps/admin-x-design-system/src/global/form/CodeEditor.tsx +++ b/apps/admin-x-design-system/src/global/form/CodeEditor.tsx @@ -1,8 +1,9 @@ import type {ReactCodeMirrorRef} from '@uiw/react-codemirror'; import React, {Suspense, forwardRef} from 'react'; import type {CodeEditorProps} from './CodeEditorView'; +import type {FetchKoenigLexical} from './HtmlEditor'; -export type {CodeEditorProps}; +export type {CodeEditorProps, FetchKoenigLexical}; // Imported asynchronously to avoid including CodeMirror in the main bundle const CodeEditorView = React.lazy(() => import('./CodeEditorView')); diff --git a/apps/admin-x-design-system/src/index.ts b/apps/admin-x-design-system/src/index.ts index 284b8e3b42..e0cbf276f9 100644 --- a/apps/admin-x-design-system/src/index.ts +++ b/apps/admin-x-design-system/src/index.ts @@ -17,7 +17,7 @@ export type {CheckboxProps} from './global/form/Checkbox'; export {default as CheckboxGroup} from './global/form/CheckboxGroup'; export type {CheckboxGroupProps} from './global/form/CheckboxGroup'; export {default as CodeEditor} from './global/form/CodeEditor'; -export type {CodeEditorProps} from './global/form/CodeEditor'; +export type {CodeEditorProps, FetchKoenigLexical} from './global/form/CodeEditor'; export {default as ColorIndicator} from './global/form/ColorIndicator'; export type {ColorIndicatorProps} from './global/form/ColorIndicator'; export {default as ColorPicker} from './global/form/ColorPicker'; diff --git a/apps/admin-x-framework/.eslintrc.cjs b/apps/admin-x-framework/.eslintrc.cjs new file mode 100644 index 0000000000..27d400ede5 --- /dev/null +++ b/apps/admin-x-framework/.eslintrc.cjs @@ -0,0 +1,41 @@ +module.exports = { + extends: [ + 'plugin:ghost/ts', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended' + ], + plugins: [ + 'ghost', + 'react-refresh', + 'tailwindcss' + ], + settings: { + react: { + version: 'detect' + } + }, + rules: { + // suppress errors for missing 'import React' in JSX files, as we don't need it + 'react/react-in-jsx-scope': 'off', + // ignore prop-types for now + 'react/prop-types': 'off', + + 'react/jsx-sort-props': ['error', { + reservedFirst: true, + callbacksLast: true, + shorthandLast: true, + locale: 'en' + }], + 'react/button-has-type': 'error', + 'react/no-array-index-key': 'error', + 'react/jsx-key': 'off', + + 'tailwindcss/classnames-order': ['error', {config: 'tailwind.config.cjs'}], + 'tailwindcss/enforces-negative-arbitrary-values': ['warn', {config: 'tailwind.config.cjs'}], + 'tailwindcss/enforces-shorthand': ['warn', {config: 'tailwind.config.cjs'}], + 'tailwindcss/migration-from-tailwind-2': ['warn', {config: 'tailwind.config.cjs'}], + 'tailwindcss/no-arbitrary-value': 'off', + 'tailwindcss/no-custom-classname': 'off', + 'tailwindcss/no-contradicting-classname': ['error', {config: 'tailwind.config.cjs'}] + } +}; diff --git a/apps/admin-x-framework/.gitignore b/apps/admin-x-framework/.gitignore new file mode 100644 index 0000000000..62ac7b71aa --- /dev/null +++ b/apps/admin-x-framework/.gitignore @@ -0,0 +1,2 @@ +es +types diff --git a/apps/admin-x-framework/package.json b/apps/admin-x-framework/package.json new file mode 100644 index 0000000000..b6f2a4b927 --- /dev/null +++ b/apps/admin-x-framework/package.json @@ -0,0 +1,80 @@ +{ + "name": "@tryghost/admin-x-framework", + "type": "module", + "version": "0.0.0", + "repository": "https://github.com/TryGhost/Ghost/tree/main/apps/admin-x-framework", + "author": "Ghost Foundation", + "private": true, + "exports": { + ".": { + "import": "./es/index.js", + "types": "./types/index.d.ts" + }, + "./errors": { + "import": "./es/errors.js", + "types": "./types/errors.d.ts" + }, + "./helpers": { + "import": "./es/helpers.js", + "types": "./types/helpers.d.ts" + }, + "./hooks": { + "import": "./es/hooks.js", + "types": "./types/hooks.d.ts" + }, + "./routing": { + "import": "./es/routing.js", + "types": "./types/routing.d.ts" + }, + "./api/*": { + "import": "./es/api/*.js", + "types": "./types/api/*.d.ts" + } + }, + "sideEffects": false, + "scripts": { + "build": "vite build && tsc -p tsconfig.declaration.json", + "prepare": "yarn build", + "test": "yarn test:types", + "test:types": "tsc --noEmit", + "lint:code": "eslint --ext .js,.ts,.cjs,.tsx src/ --cache", + "lint": "yarn lint:code && (yarn lint:test || echo \"TODO ADD TESTS TO LINT\")", + "lint:test": "eslint -c test/.eslintrc.cjs --ext .js,.ts,.cjs,.tsx test/ --cache" + }, + "files": [ + "es", + "types" + ], + "devDependencies": { + "@vitejs/plugin-react": "4.1.1", + "c8": "8.0.1", + "eslint-plugin-react-hooks": "4.6.0", + "eslint-plugin-react-refresh": "0.4.3", + "mocha": "10.2.0", + "sinon": "17.0.0", + "ts-node": "10.9.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "typescript": "5.2.2", + "vite": "4.5.0" + }, + "dependencies": { + "@sentry/react": "7.78.0", + "@tanstack/react-query": "4.36.1", + "@tryghost/admin-x-design-system": "0.0.0" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "nx": { + "targets": { + "build": { + "dependsOn": [ + "build", + {"projects": ["@tryghost/admin-x-design-system"], "target": "build"} + ] + } + } + } +} diff --git a/apps/admin-x-settings/src/api/actions.ts b/apps/admin-x-framework/src/api/actions.ts similarity index 98% rename from apps/admin-x-settings/src/api/actions.ts rename to apps/admin-x-framework/src/api/actions.ts index ed656a48fa..0694a70e38 100644 --- a/apps/admin-x-settings/src/api/actions.ts +++ b/apps/admin-x-framework/src/api/actions.ts @@ -1,7 +1,7 @@ -import {ExternalLink, InternalLink} from '../components/providers/RoutingProvider'; import {InfiniteData} from '@tanstack/react-query'; -import {JSONObject} from './config'; +import {ExternalLink, InternalLink} from '../providers/RoutingProvider'; import {Meta, createInfiniteQuery} from '../utils/api/hooks'; +import {JSONObject} from './config'; // Types diff --git a/apps/admin-x-settings/src/api/apiKeys.ts b/apps/admin-x-framework/src/api/apiKeys.ts similarity index 100% rename from apps/admin-x-settings/src/api/apiKeys.ts rename to apps/admin-x-framework/src/api/apiKeys.ts index a70ad74c62..a66ec2aedc 100644 --- a/apps/admin-x-settings/src/api/apiKeys.ts +++ b/apps/admin-x-framework/src/api/apiKeys.ts @@ -1,5 +1,5 @@ -import {IntegrationsResponseType, integrationsDataType} from './integrations'; import {createMutation} from '../utils/api/hooks'; +import {IntegrationsResponseType, integrationsDataType} from './integrations'; // Types diff --git a/apps/admin-x-settings/src/api/config.ts b/apps/admin-x-framework/src/api/config.ts similarity index 100% rename from apps/admin-x-settings/src/api/config.ts rename to apps/admin-x-framework/src/api/config.ts diff --git a/apps/admin-x-framework/src/api/currentUser.ts b/apps/admin-x-framework/src/api/currentUser.ts new file mode 100644 index 0000000000..a2093dea4f --- /dev/null +++ b/apps/admin-x-framework/src/api/currentUser.ts @@ -0,0 +1,32 @@ +import {useQuery} from '@tanstack/react-query'; +import {useEffect, useMemo} from 'react'; +import useHandleError from '../hooks/useHandleError'; +import {apiUrl, useFetchApi} from '../utils/api/fetchApi'; +import {User} from './users'; + +export const usersDataType = 'UsersResponseType'; + +// Special case where we can't use createQuery because this is used by usePermissions, which is then used by createQuery +export const useCurrentUser = () => { + const url = apiUrl('/users/me/', {include: 'roles'}); + const fetchApi = useFetchApi(); + const handleError = useHandleError(); + + const result = useQuery({ + queryKey: [usersDataType, url], + queryFn: () => fetchApi(url) + }); + + const data = useMemo(() => result.data?.users?.[0], [result.data]); + + useEffect(() => { + if (result.error) { + handleError(result.error); + } + }, [handleError, result.error]); + + return { + ...result, + data + }; +}; diff --git a/apps/admin-x-settings/src/api/customThemeSettings.ts b/apps/admin-x-framework/src/api/customThemeSettings.ts similarity index 100% rename from apps/admin-x-settings/src/api/customThemeSettings.ts rename to apps/admin-x-framework/src/api/customThemeSettings.ts index 2c59fab67e..3a1871e357 100644 --- a/apps/admin-x-settings/src/api/customThemeSettings.ts +++ b/apps/admin-x-framework/src/api/customThemeSettings.ts @@ -1,5 +1,5 @@ -import {Setting} from './settings'; import {createMutation, createQuery} from '../utils/api/hooks'; +import {Setting} from './settings'; type CustomThemeSettingData = { type: 'text', value: string | null, default: string | null } | diff --git a/apps/admin-x-settings/src/api/db.ts b/apps/admin-x-framework/src/api/db.ts similarity index 100% rename from apps/admin-x-settings/src/api/db.ts rename to apps/admin-x-framework/src/api/db.ts diff --git a/apps/admin-x-settings/src/api/emailVerification.ts b/apps/admin-x-framework/src/api/emailVerification.ts similarity index 100% rename from apps/admin-x-settings/src/api/emailVerification.ts rename to apps/admin-x-framework/src/api/emailVerification.ts diff --git a/apps/admin-x-settings/src/api/files.ts b/apps/admin-x-framework/src/api/files.ts similarity index 100% rename from apps/admin-x-settings/src/api/files.ts rename to apps/admin-x-framework/src/api/files.ts diff --git a/apps/admin-x-settings/src/api/images.ts b/apps/admin-x-framework/src/api/images.ts similarity index 100% rename from apps/admin-x-settings/src/api/images.ts rename to apps/admin-x-framework/src/api/images.ts diff --git a/apps/admin-x-settings/src/api/integrations.ts b/apps/admin-x-framework/src/api/integrations.ts similarity index 100% rename from apps/admin-x-settings/src/api/integrations.ts rename to apps/admin-x-framework/src/api/integrations.ts index 68fda881ef..e991db9802 100644 --- a/apps/admin-x-settings/src/api/integrations.ts +++ b/apps/admin-x-framework/src/api/integrations.ts @@ -1,5 +1,5 @@ -import {APIKey} from './apiKeys'; import {Meta, createMutation, createQuery} from '../utils/api/hooks'; +import {APIKey} from './apiKeys'; import {Webhook} from './webhooks'; // Types diff --git a/apps/admin-x-settings/src/api/invites.ts b/apps/admin-x-framework/src/api/invites.ts similarity index 100% rename from apps/admin-x-settings/src/api/invites.ts rename to apps/admin-x-framework/src/api/invites.ts diff --git a/apps/admin-x-settings/src/api/labels.ts b/apps/admin-x-framework/src/api/labels.ts similarity index 100% rename from apps/admin-x-settings/src/api/labels.ts rename to apps/admin-x-framework/src/api/labels.ts diff --git a/apps/admin-x-settings/src/api/members.ts b/apps/admin-x-framework/src/api/members.ts similarity index 100% rename from apps/admin-x-settings/src/api/members.ts rename to apps/admin-x-framework/src/api/members.ts diff --git a/apps/admin-x-settings/src/api/newsletters.ts b/apps/admin-x-framework/src/api/newsletters.ts similarity index 100% rename from apps/admin-x-settings/src/api/newsletters.ts rename to apps/admin-x-framework/src/api/newsletters.ts diff --git a/apps/admin-x-settings/src/api/offers.ts b/apps/admin-x-framework/src/api/offers.ts similarity index 100% rename from apps/admin-x-settings/src/api/offers.ts rename to apps/admin-x-framework/src/api/offers.ts diff --git a/apps/admin-x-settings/src/api/posts.ts b/apps/admin-x-framework/src/api/posts.ts similarity index 100% rename from apps/admin-x-settings/src/api/posts.ts rename to apps/admin-x-framework/src/api/posts.ts diff --git a/apps/admin-x-settings/src/api/recommendations.ts b/apps/admin-x-framework/src/api/recommendations.ts similarity index 100% rename from apps/admin-x-settings/src/api/recommendations.ts rename to apps/admin-x-framework/src/api/recommendations.ts diff --git a/apps/admin-x-settings/src/api/redirects.ts b/apps/admin-x-framework/src/api/redirects.ts similarity index 100% rename from apps/admin-x-settings/src/api/redirects.ts rename to apps/admin-x-framework/src/api/redirects.ts diff --git a/apps/admin-x-settings/src/api/referrers.ts b/apps/admin-x-framework/src/api/referrers.ts similarity index 100% rename from apps/admin-x-settings/src/api/referrers.ts rename to apps/admin-x-framework/src/api/referrers.ts diff --git a/apps/admin-x-settings/src/api/roles.ts b/apps/admin-x-framework/src/api/roles.ts similarity index 100% rename from apps/admin-x-settings/src/api/roles.ts rename to apps/admin-x-framework/src/api/roles.ts diff --git a/apps/admin-x-settings/src/api/routes.ts b/apps/admin-x-framework/src/api/routes.ts similarity index 100% rename from apps/admin-x-settings/src/api/routes.ts rename to apps/admin-x-framework/src/api/routes.ts diff --git a/apps/admin-x-settings/src/api/settings.ts b/apps/admin-x-framework/src/api/settings.ts similarity index 95% rename from apps/admin-x-settings/src/api/settings.ts rename to apps/admin-x-framework/src/api/settings.ts index c9614d89d3..c15dde2e89 100644 --- a/apps/admin-x-settings/src/api/settings.ts +++ b/apps/admin-x-framework/src/api/settings.ts @@ -1,5 +1,5 @@ -import {Config} from './config'; import {Meta, createMutation, createQuery} from '../utils/api/hooks'; +import {Config} from './config'; // Types @@ -49,6 +49,11 @@ export const useDeleteStripeSettings = createMutation({ invalidateQueries: {dataType} }); +export const useTestSlack = createMutation({ + method: 'POST', + path: () => '/slack/test/' +}); + // Helpers export function humanizeSettingKey(key: string) { diff --git a/apps/admin-x-settings/src/api/site.ts b/apps/admin-x-framework/src/api/site.ts similarity index 100% rename from apps/admin-x-settings/src/api/site.ts rename to apps/admin-x-framework/src/api/site.ts diff --git a/apps/admin-x-settings/src/api/staffToken.ts b/apps/admin-x-framework/src/api/staffToken.ts similarity index 100% rename from apps/admin-x-settings/src/api/staffToken.ts rename to apps/admin-x-framework/src/api/staffToken.ts diff --git a/apps/admin-x-settings/src/api/themes.ts b/apps/admin-x-framework/src/api/themes.ts similarity index 93% rename from apps/admin-x-settings/src/api/themes.ts rename to apps/admin-x-framework/src/api/themes.ts index e315739751..debf83d785 100644 --- a/apps/admin-x-settings/src/api/themes.ts +++ b/apps/admin-x-framework/src/api/themes.ts @@ -1,4 +1,3 @@ -import {OfficialTheme} from '../components/providers/ServiceProvider'; import {createMutation, createQuery} from '../utils/api/hooks'; import {customThemeSettingsDataType} from './customThemeSettings'; @@ -133,15 +132,15 @@ export function isActiveTheme(theme: Theme): boolean { return theme.active; } -export function isDefaultTheme(theme: Theme | OfficialTheme): boolean { +export function isDefaultTheme(theme: {name: string}): boolean { return theme.name.toLowerCase() === 'source'; } -export function isLegacyTheme(theme: Theme | OfficialTheme): boolean { +export function isLegacyTheme(theme: {name: string}): boolean { return theme.name.toLowerCase() === 'casper'; } -export function isDefaultOrLegacyTheme(theme: Theme | OfficialTheme): boolean { +export function isDefaultOrLegacyTheme(theme: {name: string}): boolean { return isDefaultTheme(theme) || isLegacyTheme(theme); } diff --git a/apps/admin-x-settings/src/api/tiers.ts b/apps/admin-x-framework/src/api/tiers.ts similarity index 100% rename from apps/admin-x-settings/src/api/tiers.ts rename to apps/admin-x-framework/src/api/tiers.ts diff --git a/apps/admin-x-settings/src/api/users.ts b/apps/admin-x-framework/src/api/users.ts similarity index 92% rename from apps/admin-x-settings/src/api/users.ts rename to apps/admin-x-framework/src/api/users.ts index 7015e546bb..41ad4efad8 100644 --- a/apps/admin-x-settings/src/api/users.ts +++ b/apps/admin-x-framework/src/api/users.ts @@ -1,7 +1,8 @@ import {InfiniteData} from '@tanstack/react-query'; -import {Meta, createInfiniteQuery, createMutation, createQuery} from '../utils/api/hooks'; -import {UserRole} from './roles'; +import {Meta, createInfiniteQuery, createMutation} from '../utils/api/hooks'; import {deleteFromQueryCache, updateQueryCache} from '../utils/api/updateQueries'; +import {usersDataType} from './currentUser'; +import {UserRole} from './roles'; // Types @@ -62,7 +63,7 @@ export interface DeleteUserResponse { // Requests -const dataType = 'UsersResponseType'; +const dataType = usersDataType; export const useBrowseUsers = createInfiniteQuery({ dataType, @@ -85,13 +86,6 @@ export const useBrowseUsers = createInfiniteQuery({ - dataType, - path: '/users/me/', - defaultSearchParams: {include: 'roles'}, - returnData: originalData => (originalData as UsersResponseType).users?.[0] -}); - export const useEditUser = createMutation({ method: 'PUT', path: user => `/users/${user.id}/`, diff --git a/apps/admin-x-settings/src/api/webhooks.ts b/apps/admin-x-framework/src/api/webhooks.ts similarity index 100% rename from apps/admin-x-settings/src/api/webhooks.ts rename to apps/admin-x-framework/src/api/webhooks.ts index e48752d118..da7ff9f928 100644 --- a/apps/admin-x-settings/src/api/webhooks.ts +++ b/apps/admin-x-framework/src/api/webhooks.ts @@ -1,5 +1,5 @@ -import {IntegrationsResponseType, integrationsDataType} from './integrations'; import {Meta, createMutation} from '../utils/api/hooks'; +import {IntegrationsResponseType, integrationsDataType} from './integrations'; // Types diff --git a/apps/admin-x-framework/src/errors.ts b/apps/admin-x-framework/src/errors.ts new file mode 100644 index 0000000000..1398926415 --- /dev/null +++ b/apps/admin-x-framework/src/errors.ts @@ -0,0 +1,2 @@ +export * from './utils/errors'; + diff --git a/apps/admin-x-framework/src/helpers.ts b/apps/admin-x-framework/src/helpers.ts new file mode 100644 index 0000000000..dea77bdd0c --- /dev/null +++ b/apps/admin-x-framework/src/helpers.ts @@ -0,0 +1,2 @@ +export * from './utils/helpers'; + diff --git a/apps/admin-x-framework/src/hooks.ts b/apps/admin-x-framework/src/hooks.ts new file mode 100644 index 0000000000..91a9b37b49 --- /dev/null +++ b/apps/admin-x-framework/src/hooks.ts @@ -0,0 +1,4 @@ +export {default as useFilterableApi} from './hooks/useFilterableApi'; +export {default as useHandleError} from './hooks/useHandleError'; +export {usePermission} from './hooks/usePermissions'; + diff --git a/apps/admin-x-settings/src/hooks/useFilterableApi.ts b/apps/admin-x-framework/src/hooks/useFilterableApi.ts similarity index 95% rename from apps/admin-x-settings/src/hooks/useFilterableApi.ts rename to apps/admin-x-framework/src/hooks/useFilterableApi.ts index 7a5cfb558e..6b738e8cd9 100644 --- a/apps/admin-x-settings/src/hooks/useFilterableApi.ts +++ b/apps/admin-x-framework/src/hooks/useFilterableApi.ts @@ -1,5 +1,6 @@ -import {Meta, apiUrl, useFetchApi} from '../utils/api/hooks'; import {useRef} from 'react'; +import {apiUrl, useFetchApi} from '../utils/api/fetchApi'; +import {Meta} from '../utils/api/hooks'; const escapeNqlString = (value: string) => { return '\'' + value.replace(/'/g, '\\\'') + '\''; diff --git a/apps/admin-x-settings/src/utils/api/handleError.ts b/apps/admin-x-framework/src/hooks/useHandleError.ts similarity index 90% rename from apps/admin-x-settings/src/utils/api/handleError.ts rename to apps/admin-x-framework/src/hooks/useHandleError.ts index 2cba116966..e041fe4694 100644 --- a/apps/admin-x-settings/src/utils/api/handleError.ts +++ b/apps/admin-x-framework/src/hooks/useHandleError.ts @@ -1,9 +1,9 @@ import * as Sentry from '@sentry/react'; -import toast from 'react-hot-toast'; -import {APIError, JSONError, ValidationError} from '../errors'; import {showToast} from '@tryghost/admin-x-design-system'; import {useCallback} from 'react'; -import {useSentryDSN} from '../../components/providers/ServiceProvider'; +import toast from 'react-hot-toast'; +import {useFramework} from '../providers/FrameworkProvider'; +import {APIError, JSONError, ValidationError} from '../utils/errors'; /** * Generic error handling for API calls. This is enabled by default for queries (can be disabled by @@ -11,7 +11,7 @@ import {useSentryDSN} from '../../components/providers/ServiceProvider'; * errors in order to handle anything unexpected. */ const useHandleError = () => { - const sentryDSN = useSentryDSN(); + const {sentryDSN} = useFramework(); /** * @param error Thrown error. @@ -20,7 +20,7 @@ const useHandleError = () => { * so this toast is intended as a worst-case fallback message when we don't know what else to do. * */ - type HandleErrorReturnType = void | any; + type HandleErrorReturnType = void | any; // eslint-disable-line @typescript-eslint/no-explicit-any const handleError = useCallback((error: unknown, {withToast = true}: {withToast?: boolean} = {}) : HandleErrorReturnType => { // eslint-disable-next-line no-console console.error(error); diff --git a/apps/admin-x-settings/src/hooks/usePermissions.ts b/apps/admin-x-framework/src/hooks/usePermissions.ts similarity index 73% rename from apps/admin-x-settings/src/hooks/usePermissions.ts rename to apps/admin-x-framework/src/hooks/usePermissions.ts index ef1e33815c..e1e68ae6a0 100644 --- a/apps/admin-x-settings/src/hooks/usePermissions.ts +++ b/apps/admin-x-framework/src/hooks/usePermissions.ts @@ -1,8 +1,8 @@ +import {useCurrentUser} from '../api/currentUser'; import {UserRoleType} from '../api/roles'; -import {useGlobalData} from '../components/providers/GlobalDataProvider'; export const usePermission = (userRoles:string[]) => { - const {currentUser} = useGlobalData(); + const {data: currentUser} = useCurrentUser(); const currentUserRoles = currentUser?.roles.map(role => role.name); if (!currentUserRoles) { return false; diff --git a/apps/admin-x-framework/src/index.ts b/apps/admin-x-framework/src/index.ts new file mode 100644 index 0000000000..f6935b425f --- /dev/null +++ b/apps/admin-x-framework/src/index.ts @@ -0,0 +1,6 @@ +export {default as FrameworkProvider, useFramework} from './providers/FrameworkProvider'; +export type {FrameworkContextType, FrameworkProviderProps} from './providers/FrameworkProvider'; + +export {useQueryClient} from '@tanstack/react-query'; +export type {InfiniteData} from '@tanstack/react-query'; + diff --git a/apps/admin-x-framework/src/providers/FrameworkProvider.tsx b/apps/admin-x-framework/src/providers/FrameworkProvider.tsx new file mode 100644 index 0000000000..3a04d1d817 --- /dev/null +++ b/apps/admin-x-framework/src/providers/FrameworkProvider.tsx @@ -0,0 +1,60 @@ +import {ErrorBoundary as SentryErrorBoundary} from '@sentry/react'; +import {QueryClientProvider} from '@tanstack/react-query'; +import {ReactNode, createContext, useContext} from 'react'; +import queryClient from '../utils/queryClient'; +import RoutingProvider, {RoutingProviderProps} from './RoutingProvider'; + +export interface FrameworkProviderProps { + basePath: string; + ghostVersion: string; + externalNavigate: RoutingProviderProps['externalNavigate']; + modals: RoutingProviderProps['modals']; + unsplashConfig: { + Authorization: string; + 'Accept-Version': string; + 'Content-Type': string; + 'App-Pragma': string; + 'X-Unsplash-Cache': boolean; + }; + sentryDSN: string | null; + onUpdate: (dataType: string, response: unknown) => void; + onInvalidate: (dataType: string) => void; + onDelete: (dataType: string, id: string) => void; + + children: ReactNode; +} + +export type FrameworkContextType = Omit; + +const FrameworkContext = createContext({ + ghostVersion: '', + unsplashConfig: { + Authorization: '', + 'Accept-Version': '', + 'Content-Type': '', + 'App-Pragma': '', + 'X-Unsplash-Cache': true + }, + sentryDSN: null, + onUpdate: () => {}, + onInvalidate: () => {}, + onDelete: () => {} +}); + +function FrameworkProvider({externalNavigate, basePath, modals, children, ...props}: FrameworkProviderProps) { + return ( + + + + + {children} + + + + + ); +} + +export default FrameworkProvider; + +export const useFramework = () => useContext(FrameworkContext); diff --git a/apps/admin-x-settings/src/components/providers/RoutingProvider.tsx b/apps/admin-x-framework/src/providers/RoutingProvider.tsx similarity index 59% rename from apps/admin-x-settings/src/components/providers/RoutingProvider.tsx rename to apps/admin-x-framework/src/providers/RoutingProvider.tsx index 0c5bea557e..84af39f483 100644 --- a/apps/admin-x-settings/src/components/providers/RoutingProvider.tsx +++ b/apps/admin-x-framework/src/providers/RoutingProvider.tsx @@ -1,9 +1,7 @@ -import NiceModal from '@ebay/nice-modal-react'; -import React, {createContext, useCallback, useEffect, useState} from 'react'; -import {useScrollSectionContext} from '../../hooks/useScrollSection'; -import type {ModalComponent, ModalName} from './routing/modals'; +import NiceModal, {NiceModalHocProps} from '@ebay/nice-modal-react'; +import React, {createContext, useCallback, useContext, useEffect, useState} from 'react'; -export type RouteParams = {[key: string]: string} +export type RouteParams = Record export type ExternalLink = { isExternal: true; @@ -16,63 +14,36 @@ export type InternalLink = { route: string; } -export type RoutingContextData = { - route: string; - updateRoute: (to: string | InternalLink | ExternalLink) => void; - loadingModal: boolean; -}; - -export const RouteContext = createContext({ - route: '', - updateRoute: () => {}, - loadingModal: false -}); - export type RoutingModalProps = { pathName: string; params?: Record, searchParams?: URLSearchParams } -const modalPaths: {[key: string]: ModalName} = { - 'design/change-theme': 'DesignAndThemeModal', - 'design/edit': 'DesignAndThemeModal', - // this is a special route, because it can install a theme directly from the Ghost Marketplace - 'design/change-theme/install': 'DesignAndThemeModal', - 'navigation/edit': 'NavigationModal', - 'staff/invite': 'InviteUserModal', - 'staff/:slug': 'UserDetailModal', - 'portal/edit': 'PortalModal', - 'tiers/add': 'TierDetailModal', - 'tiers/:id': 'TierDetailModal', - 'stripe-connect': 'StripeConnectModal', - 'newsletters/new': 'AddNewsletterModal', - 'newsletters/:id': 'NewsletterDetailModal', - 'history/view': 'HistoryModal', - 'history/view/:user': 'HistoryModal', - 'integrations/zapier': 'ZapierModal', - 'integrations/slack': 'SlackModal', - 'integrations/amp': 'AmpModal', - 'integrations/unsplash': 'UnsplashModal', - 'integrations/firstpromoter': 'FirstpromoterModal', - 'integrations/pintura': 'PinturaModal', - 'integrations/new': 'AddIntegrationModal', - 'integrations/:id': 'CustomIntegrationModal', - 'recommendations/add': 'AddRecommendationModal', - 'recommendations/edit': 'EditRecommendationModal', - 'announcement-bar/edit': 'AnnouncementBarModal', - 'embed-signup-form/show': 'EmbedSignupFormModal', - 'offers/edit': 'OffersModal', - 'offers/new': 'AddOfferModal', - 'offers/:id': 'EditOfferModal', - about: 'AboutModal' +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ModalsModule = {default: {[key: string]: ModalComponent}} + +export type ModalComponent = React.FC; + +export type RoutingContextData = { + route: string; + updateRoute: (to: string | InternalLink | ExternalLink) => void; + loadingModal: boolean; + eventTarget: EventTarget; }; -function getHashPath(urlPath: string | undefined) { +export const RouteContext = createContext({ + route: '', + updateRoute: () => {}, + loadingModal: false, + eventTarget: new EventTarget() +}); + +function getHashPath(basePath: string, urlPath: string | undefined) { if (!urlPath) { return null; } - const regex = /\/settings\/(.*)/; + const regex = new RegExp(`/${basePath}/(.*)`); const match = urlPath?.match(regex); if (match) { @@ -82,19 +53,19 @@ function getHashPath(urlPath: string | undefined) { return null; } -const handleNavigation = (currentRoute: string | undefined) => { +const handleNavigation = (basePath: string, currentRoute: string | undefined, loadModals?: () => Promise, modalPaths?: Record) => { // Get the hash from the URL let hash = window.location.hash; hash = hash.substring(1); // Create a URL to easily extract the path without query parameters const domain = `${window.location.protocol}//${window.location.hostname}`; - let url = new URL(hash, domain); + const url = new URL(hash, domain); - const pathName = getHashPath(url.pathname); + const pathName = getHashPath(basePath, url.pathname); const searchParams = url.searchParams; - if (pathName) { + if (pathName && modalPaths && loadModals) { const [, currentModalName] = Object.entries(modalPaths).find(([modalPath]) => matchRoute(currentRoute || '', modalPath)) || []; const [path, modalName] = Object.entries(modalPaths).find(([modalPath]) => matchRoute(pathName, modalPath)) || []; @@ -102,7 +73,7 @@ const handleNavigation = (currentRoute: string | undefined) => { pathName, changingModal: modalName && modalName !== currentModalName, modal: (path && modalName) ? // we should consider adding '&& modalName !== currentModalName' here, but this breaks tests - import('./routing/modals').then(({default: modals}) => { + loadModals().then(({default: modals}) => { NiceModal.show(modals[modalName] as ModalComponent, {pathName, params: matchRoute(pathName, path), searchParams}); }) : undefined @@ -119,22 +90,17 @@ const matchRoute = (pathname: string, routeDefinition: string) => { } }; -type RouteProviderProps = { +export interface RoutingProviderProps { + basePath: string; externalNavigate: (link: ExternalLink) => void; + modals?: {paths: Record, load: () => Promise} children: React.ReactNode; -}; +} -const RoutingProvider: React.FC = ({externalNavigate, children}) => { +const RoutingProvider: React.FC = ({basePath, externalNavigate, modals, children}) => { const [route, setRoute] = useState(undefined); const [loadingModal, setLoadingModal] = useState(false); - const {updateNavigatedSection, scrollToSection} = useScrollSectionContext(); - - useEffect(() => { - // Preload all the modals after initial render to avoid a delay when opening them - setTimeout(() => { - import('./routing/modals'); - }, 1000); - }, []); + const [eventTarget] = useState(new EventTarget()); const updateRoute = useCallback((to: string | InternalLink | ExternalLink) => { const options = typeof to === 'string' ? {route: to} : to; @@ -147,18 +113,27 @@ const RoutingProvider: React.FC = ({externalNavigate, childr const newPath = options.route; if (newPath === route) { - scrollToSection(newPath.split('/')[0]); + // No change } else if (newPath) { window.location.hash = `/settings/${newPath}`; } else { window.location.hash = `/settings`; } - }, [externalNavigate, route, scrollToSection]); + + eventTarget.dispatchEvent(new CustomEvent('routeChange', {detail: {newPath, oldPath: route}})); + }, [eventTarget, externalNavigate, route]); + + useEffect(() => { + // Preload all the modals after initial render to avoid a delay when opening them + setTimeout(() => { + modals?.load(); + }, 1000); + }, []); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { const handleHashChange = () => { setRoute((currentRoute) => { - const {pathName, modal, changingModal} = handleNavigation(currentRoute); + const {pathName, modal, changingModal} = handleNavigation(basePath, currentRoute, modals?.load, modals?.paths); if (modal && changingModal) { setLoadingModal(true); @@ -178,12 +153,6 @@ const RoutingProvider: React.FC = ({externalNavigate, childr }; }, []); // eslint-disable-line react-hooks/exhaustive-deps - useEffect(() => { - if (route !== undefined) { - updateNavigatedSection(route.split('/')[0]); - } - }, [route, updateNavigatedSection]); - if (route === undefined) { return null; } @@ -193,7 +162,8 @@ const RoutingProvider: React.FC = ({externalNavigate, childr value={{ route, updateRoute, - loadingModal + loadingModal, + eventTarget }} > {children} @@ -202,3 +172,25 @@ const RoutingProvider: React.FC = ({externalNavigate, childr }; export default RoutingProvider; + +export function useRouting() { + return useContext(RouteContext); +} + +export function useRouteChangeCallback(callback: (newPath: string, oldPath: string) => void, deps: React.DependencyList) { + const {eventTarget} = useRouting(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + const stableCallback = useCallback(callback, deps); + + useEffect(() => { + const listener: EventListener = (e) => { + const event = e as CustomEvent<{newPath: string, oldPath: string}>; + stableCallback(event.detail.newPath, event.detail.oldPath); + }; + + eventTarget.addEventListener('routeChange', listener); + + return () => eventTarget.removeEventListener('routeChange', listener); + }, [eventTarget, stableCallback]); +} diff --git a/apps/admin-x-framework/src/routing.ts b/apps/admin-x-framework/src/routing.ts new file mode 100644 index 0000000000..7dc623213f --- /dev/null +++ b/apps/admin-x-framework/src/routing.ts @@ -0,0 +1,3 @@ +export {useRouteChangeCallback, useRouting} from './providers/RoutingProvider'; +export type {ExternalLink, InternalLink, RoutingModalProps} from './providers/RoutingProvider'; + diff --git a/apps/admin-x-framework/src/utils/api/fetchApi.ts b/apps/admin-x-framework/src/utils/api/fetchApi.ts new file mode 100644 index 0000000000..ce5196ea22 --- /dev/null +++ b/apps/admin-x-framework/src/utils/api/fetchApi.ts @@ -0,0 +1,125 @@ +import * as Sentry from '@sentry/react'; +import {useFramework} from '../../providers/FrameworkProvider'; +import {APIError, MaintenanceError, ServerUnreachableError, TimeoutError} from '../errors'; +import {getGhostPaths} from '../helpers'; +import handleResponse from './handleResponse'; + +export interface RequestOptions { + method?: string; + body?: string | FormData; + headers?: { + 'Content-Type'?: string; + }; + credentials?: 'include' | 'omit' | 'same-origin'; + timeout?: number; + retry?: boolean; +} + +export const useFetchApi = () => { + const {ghostVersion, sentryDSN} = useFramework(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return async (endpoint: string | URL, options: RequestOptions = {}): Promise => { + // By default, we set the Content-Type header to application/json + const defaultHeaders: Record = { + 'app-pragma': 'no-cache', + 'x-ghost-version': ghostVersion + }; + if (typeof options.body === 'string') { + defaultHeaders['content-type'] = 'application/json'; + } + const headers = options?.headers || {}; + + const controller = new AbortController(); + const {timeout} = options; + + if (timeout) { + setTimeout(() => controller.abort(), timeout); + } + + // attempt retries for 15 seconds in two situations: + // 1. Server Unreachable error from the browser (code 0 or TypeError), typically from short internet blips + // 2. Maintenance error from Ghost, upgrade in progress so API is temporarily unavailable + let attempts = 0; + const shouldRetry = options.retry === true || options.retry === undefined; + let retryingMs = 0; + const startTime = Date.now(); + const maxRetryingMs = 15_000; + const retryPeriods = [500, 1000]; + const retryableErrors = [ServerUnreachableError, MaintenanceError, TypeError]; + + const getErrorData = (error?: APIError, response?: Response) => { + const data: Record = { + errorName: error?.name, + attempts, + totalSeconds: retryingMs / 1000, + endpoint: endpoint.toString() + }; + if (endpoint.toString().includes('/ghost/api/')) { + data.server = response?.headers.get('server'); + } + return data; + }; + + while (attempts === 0 || shouldRetry) { + try { + const response = await fetch(endpoint, { + headers: { + ...defaultHeaders, + ...headers + }, + method: 'GET', + mode: 'cors', + credentials: 'include', + signal: controller.signal, + ...options + }); + + if (attempts !== 0 && sentryDSN) { + Sentry.captureMessage('Request took multiple attempts', {extra: getErrorData()}); + } + + return handleResponse(response) as ResponseData; + } catch (error) { + retryingMs = Date.now() - startTime; + + if (shouldRetry && (import.meta.env.MODE !== 'development' && retryableErrors.some(errorClass => error instanceof errorClass) && retryingMs <= maxRetryingMs)) { + await new Promise((resolve) => { + setTimeout(resolve, retryPeriods[attempts] || retryPeriods[retryPeriods.length - 1]); + }); + attempts += 1; + continue; + } + + if (attempts !== 0 && sentryDSN) { + Sentry.captureMessage('Request failed after multiple attempts', {extra: getErrorData()}); + } + + if (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') { + throw new TimeoutError(); + } + + let newError = error; + + if (!(error instanceof APIError)) { + newError = new ServerUnreachableError({cause: error}); + } + + throw newError; + }; + } + + // Used for type checking + // this can't happen, but TS isn't smart enough to undeerstand that the loop will never exit without an error or return + // because of shouldRetry + attemps usage combination + return undefined as never; + }; +}; + +const {apiRoot} = getGhostPaths(); + +export const apiUrl = (path: string, searchParams: Record = {}) => { + const url = new URL(`${apiRoot}${path}`, window.location.origin); + url.search = new URLSearchParams(searchParams).toString(); + return url.toString(); +}; diff --git a/apps/admin-x-settings/src/utils/api/handleResponse.ts b/apps/admin-x-framework/src/utils/api/handleResponse.ts similarity index 100% rename from apps/admin-x-settings/src/utils/api/handleResponse.ts rename to apps/admin-x-framework/src/utils/api/handleResponse.ts diff --git a/apps/admin-x-settings/src/utils/api/hooks.ts b/apps/admin-x-framework/src/utils/api/hooks.ts similarity index 62% rename from apps/admin-x-settings/src/utils/api/hooks.ts rename to apps/admin-x-framework/src/utils/api/hooks.ts index 082cc5cdc3..3b217880e9 100644 --- a/apps/admin-x-settings/src/utils/api/hooks.ts +++ b/apps/admin-x-framework/src/utils/api/hooks.ts @@ -1,13 +1,10 @@ -import * as Sentry from '@sentry/react'; -import handleResponse from './handleResponse'; -import useHandleError from './handleError'; -import {APIError, MaintenanceError, ServerUnreachableError, TimeoutError} from '../errors'; -import {UseInfiniteQueryOptions, UseQueryOptions, useInfiniteQuery, useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; -import {getGhostPaths} from '../helpers'; -import {useCallback, useEffect, useMemo, useState} from 'react'; +import {UseInfiniteQueryOptions, UseQueryOptions, UseQueryResult, useInfiniteQuery, useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import {usePagination} from '@tryghost/admin-x-design-system'; +import {useCallback, useEffect, useMemo, useState} from 'react'; +import useHandleError from '../../hooks/useHandleError'; import {usePermission} from '../../hooks/usePermissions'; -import {useSentryDSN, useServices} from '../../components/providers/ServiceProvider'; +import {useFramework} from '../../providers/FrameworkProvider'; +import {RequestOptions, apiUrl, useFetchApi} from './fetchApi'; export interface Meta { pagination: { @@ -20,127 +17,6 @@ export interface Meta { } } -interface RequestOptions { - method?: string; - body?: string | FormData; - headers?: { - 'Content-Type'?: string; - }; - credentials?: 'include' | 'omit' | 'same-origin'; - timeout?: number; - retry?: boolean; -} - -export const useFetchApi = () => { - const {ghostVersion} = useServices(); - const sentryDSN = useSentryDSN(); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return async (endpoint: string | URL, options: RequestOptions = {}): Promise => { - // By default, we set the Content-Type header to application/json - const defaultHeaders: Record = { - 'app-pragma': 'no-cache', - 'x-ghost-version': ghostVersion - }; - if (typeof options.body === 'string') { - defaultHeaders['content-type'] = 'application/json'; - } - const headers = options?.headers || {}; - - const controller = new AbortController(); - const {timeout} = options; - - if (timeout) { - setTimeout(() => controller.abort(), timeout); - } - - // attempt retries for 15 seconds in two situations: - // 1. Server Unreachable error from the browser (code 0 or TypeError), typically from short internet blips - // 2. Maintenance error from Ghost, upgrade in progress so API is temporarily unavailable - let attempts = 0; - let shouldRetry = options.retry === true || options.retry === undefined; - let retryingMs = 0; - const startTime = Date.now(); - const maxRetryingMs = 15_000; - const retryPeriods = [500, 1000]; - const retryableErrors = [ServerUnreachableError, MaintenanceError, TypeError]; - - const getErrorData = (error?: APIError, response?: Response) => { - const data: Record = { - errorName: error?.name, - attempts, - totalSeconds: retryingMs / 1000, - endpoint: endpoint.toString() - }; - if (endpoint.toString().includes('/ghost/api/')) { - data.server = response?.headers.get('server'); - } - return data; - }; - - while (attempts === 0 || shouldRetry) { - try { - const response = await fetch(endpoint, { - headers: { - ...defaultHeaders, - ...headers - }, - method: 'GET', - mode: 'cors', - credentials: 'include', - signal: controller.signal, - ...options - }); - - if (attempts !== 0 && sentryDSN) { - Sentry.captureMessage('Request took multiple attempts', {extra: getErrorData()}); - } - - return handleResponse(response) as ResponseData; - } catch (error) { - retryingMs = Date.now() - startTime; - - if (shouldRetry && (import.meta.env.MODE !== 'development' && retryableErrors.some(errorClass => error instanceof errorClass) && retryingMs <= maxRetryingMs)) { - await new Promise((resolve) => { - setTimeout(resolve, retryPeriods[attempts] || retryPeriods[retryPeriods.length - 1]); - }); - attempts += 1; - continue; - } - - if (attempts !== 0 && sentryDSN) { - Sentry.captureMessage('Request failed after multiple attempts', {extra: getErrorData()}); - } - - if (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') { - throw new TimeoutError(); - } - - let newError = error; - - if (!(error instanceof APIError)) { - newError = new ServerUnreachableError({cause: error}); - } - - throw newError; - }; - } - - // Used for type checking - // this can't happen, but TS isn't smart enough to undeerstand that the loop will never exit without an error or return - // because of shouldRetry + attemps usage combination - return undefined as never; - }; -}; - -const {apiRoot} = getGhostPaths(); - -export const apiUrl = (path: string, searchParams: Record = {}) => { - const url = new URL(`${apiRoot}${path}`, window.location.origin); - url.search = new URLSearchParams(searchParams).toString(); - return url.toString(); -}; - const parameterizedPath = (path: string, params: string | string[]) => { const paramList = Array.isArray(params) ? params : [params]; return paramList.reduce(function (updatedPath, param) { @@ -163,7 +39,7 @@ type QueryHookOptions = UseQueryOptions & { defaultErrorHandler?: boolean; }; -export const createQuery = (options: QueryOptions) => ({searchParams, ...query}: QueryHookOptions = {}) => { +export const createQuery = (options: QueryOptions) => ({searchParams, ...query}: QueryHookOptions = {}): Omit, 'data'> & {data: ResponseData | undefined} => { const url = apiUrl(options.path, searchParams || options.defaultSearchParams); const fetchApi = useFetchApi(); const handleError = useHandleError(); @@ -311,7 +187,7 @@ const mutate = ({fetchApi, path, payload, searchParams, o export const createMutation = (options: MutationOptions) => () => { const fetchApi = useFetchApi(); const queryClient = useQueryClient(); - const {onUpdate, onInvalidate, onDelete} = useServices(); + const {onUpdate, onInvalidate, onDelete} = useFramework(); const afterMutate = useCallback((newData: ResponseData, payload: Payload) => { if (options.invalidateQueries) { diff --git a/apps/admin-x-settings/src/utils/api/updateQueries.ts b/apps/admin-x-framework/src/utils/api/updateQueries.ts similarity index 100% rename from apps/admin-x-settings/src/utils/api/updateQueries.ts rename to apps/admin-x-framework/src/utils/api/updateQueries.ts diff --git a/apps/admin-x-settings/src/utils/errors.ts b/apps/admin-x-framework/src/utils/errors.ts similarity index 100% rename from apps/admin-x-settings/src/utils/errors.ts rename to apps/admin-x-framework/src/utils/errors.ts diff --git a/apps/admin-x-framework/src/utils/helpers.ts b/apps/admin-x-framework/src/utils/helpers.ts new file mode 100644 index 0000000000..d05d5a4ef4 --- /dev/null +++ b/apps/admin-x-framework/src/utils/helpers.ts @@ -0,0 +1,32 @@ +export interface IGhostPaths { + subdir: string; + adminRoot: string; + assetRoot: string; + apiRoot: string; +} + +export function getGhostPaths(): IGhostPaths { + const path = window.location.pathname; + const subdir = path.substr(0, path.search('/ghost/')); + const adminRoot = `${subdir}/ghost/`; + const assetRoot = `${subdir}/ghost/assets/`; + const apiRoot = `${subdir}/ghost/api/admin`; + return {subdir, adminRoot, assetRoot, apiRoot}; +} + +export function downloadFile(url: string) { + let iframe = document.getElementById('iframeDownload'); + + if (!iframe) { + iframe = document.createElement('iframe'); + iframe.id = 'iframeDownload'; + iframe.style.display = 'none'; + document.body.append(iframe); + } + + iframe.setAttribute('src', url); +} + +export function downloadFromEndpoint(path: string) { + downloadFile(`${getGhostPaths().apiRoot}${path}`); +} diff --git a/apps/admin-x-framework/src/utils/queryClient.ts b/apps/admin-x-framework/src/utils/queryClient.ts new file mode 100644 index 0000000000..7a3197e916 --- /dev/null +++ b/apps/admin-x-framework/src/utils/queryClient.ts @@ -0,0 +1,25 @@ +import {QueryClient} from '@tanstack/react-query'; + +declare global { + interface Window { + adminXQueryClient?: QueryClient; + } +} + +const queryClient = window.adminXQueryClient || new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + staleTime: 5 * (60 * 1000), // 5 mins + cacheTime: 10 * (60 * 1000), // 10 mins + // We have custom retry logic for specific errors in fetchApi() + retry: false + } + } +}); + +if (!window.adminXQueryClient) { + window.adminXQueryClient = queryClient; +} + +export default queryClient; diff --git a/apps/admin-x-framework/test/.eslintrc.cjs b/apps/admin-x-framework/test/.eslintrc.cjs new file mode 100644 index 0000000000..6fe6dc1504 --- /dev/null +++ b/apps/admin-x-framework/test/.eslintrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + parser: '@typescript-eslint/parser', + plugins: ['ghost'], + extends: [ + 'plugin:ghost/test' + ] +}; diff --git a/apps/admin-x-framework/test/hello.test.ts b/apps/admin-x-framework/test/hello.test.ts new file mode 100644 index 0000000000..e66b88fad4 --- /dev/null +++ b/apps/admin-x-framework/test/hello.test.ts @@ -0,0 +1,8 @@ +import assert from 'assert/strict'; + +describe('Hello world', function () { + it('Runs a test', function () { + // TODO: Write me! + assert.ok(require('../')); + }); +}); diff --git a/apps/admin-x-framework/tsconfig.declaration.json b/apps/admin-x-framework/tsconfig.declaration.json new file mode 100644 index 0000000000..294527b1a6 --- /dev/null +++ b/apps/admin-x-framework/tsconfig.declaration.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "declarationDir": "./types" + }, + "include": ["src"], + "exclude": ["src/**/*.stories.tsx"] +} diff --git a/apps/admin-x-framework/tsconfig.json b/apps/admin-x-framework/tsconfig.json new file mode 100644 index 0000000000..ee1c2d7d2d --- /dev/null +++ b/apps/admin-x-framework/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "skipLibCheck": true, + "types": ["vite/client"], + + /* Bundler mode */ + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] + } diff --git a/apps/admin-x-framework/tsconfig.node.json b/apps/admin-x-framework/tsconfig.node.json new file mode 100644 index 0000000000..364bc0ea55 --- /dev/null +++ b/apps/admin-x-framework/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts", "package.json"] +} diff --git a/apps/admin-x-framework/vite.config.ts b/apps/admin-x-framework/vite.config.ts new file mode 100644 index 0000000000..ecbd265e72 --- /dev/null +++ b/apps/admin-x-framework/vite.config.ts @@ -0,0 +1,63 @@ +import react from '@vitejs/plugin-react'; +import glob from 'glob'; +import { resolve } from 'path'; +import { defineConfig } from 'vitest/config'; + +// https://vitejs.dev/config/ +export default (function viteConfig() { + return defineConfig({ + plugins: [ + react() + ], + define: { + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + 'process.env.VITEST_SEGFAULT_RETRY': 3 + }, + preview: { + port: 4174 + }, + build: { + minify: false, + sourcemap: true, + outDir: 'es', + lib: { + formats: ['es'], + entry: glob.sync(resolve(__dirname, 'src/**/*.{ts,tsx}')).reduce((entries, path) => { + if (path.endsWith('.d.ts')) { + return entries; + } + + const outPath = path.replace(resolve(__dirname, 'src') + '/', '').replace(/\.(ts|tsx)$/, ''); + entries[outPath] = path; + return entries; + }, {} as Record) + }, + commonjsOptions: { + include: [/packages/, /node_modules/] + }, + rollupOptions: { + external: (source) => { + if (source.startsWith('.')) { + return false; + } + + if (source.includes('node_modules')) { + return true; + } + + return !source.includes(__dirname); + } + } + }, + test: { + globals: true, // required for @testing-library/jest-dom extensions + environment: 'jsdom', + include: ['./test/unit/**/*'], + testTimeout: process.env.TIMEOUT ? parseInt(process.env.TIMEOUT) : 10000, + ...(process.env.CI && { // https://github.com/vitest-dev/vitest/issues/1674 + minThreads: 1, + maxThreads: 2 + }) + } + }); +}); diff --git a/apps/admin-x-settings/package.json b/apps/admin-x-settings/package.json index 98b35e1e20..972199f9b7 100644 --- a/apps/admin-x-settings/package.json +++ b/apps/admin-x-settings/package.json @@ -38,7 +38,6 @@ }, "dependencies": { "@codemirror/lang-html": "^6.4.5", - "@tanstack/react-query": "4.36.1", "@tryghost/color-utils": "0.2.0", "@tryghost/limit-service": "^1.2.10", "@tryghost/nql": "0.11.0", @@ -50,6 +49,7 @@ "devDependencies": { "@playwright/test": "1.38.1", "@tryghost/admin-x-design-system": "0.0.0", + "@tryghost/admin-x-framework": "0.0.0", "@types/react": "18.2.37", "@types/react-dom": "18.2.15", "@types/validator": "13.11.6", @@ -68,13 +68,13 @@ "build": { "dependsOn": [ "build", - {"projects": ["@tryghost/admin-x-design-system"], "target": "build"} + {"projects": ["@tryghost/admin-x-design-system", "@tryghost/admin-x-framework"], "target": "build"} ] }, "test:acceptance": { "dependsOn": [ "test:acceptance", - {"projects": ["@tryghost/admin-x-design-system"], "target": "build"} + {"projects": ["@tryghost/admin-x-design-system", "@tryghost/admin-x-framework"], "target": "build"} ] } } diff --git a/apps/admin-x-settings/src/App.tsx b/apps/admin-x-settings/src/App.tsx index 3465fda480..19049d79ba 100644 --- a/apps/admin-x-settings/src/App.tsx +++ b/apps/admin-x-settings/src/App.tsx @@ -1,62 +1,28 @@ -import GlobalDataProvider from './components/providers/GlobalDataProvider'; import MainContent from './MainContent'; -import RoutingProvider, {ExternalLink} from './components/providers/RoutingProvider'; -import {DefaultHeaderTypes} from './unsplash/UnsplashTypes'; -import {DesignSystemApp} from '@tryghost/admin-x-design-system'; -import {FetchKoenigLexical, OfficialTheme, ServicesProvider} from './components/providers/ServiceProvider'; -import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; -import {ScrollSectionProvider} from './hooks/useScrollSection'; -import {ErrorBoundary as SentryErrorBoundary} from '@sentry/react'; -import {UpgradeStatusType} from './utils/globalTypes'; +import SettingsAppProvider, {OfficialTheme, UpgradeStatusType} from './components/providers/SettingsAppProvider'; +import SettingsRouter, {loadModals, modalPaths} from './components/providers/SettingsRouter'; +import {DesignSystemApp, FetchKoenigLexical} from '@tryghost/admin-x-design-system'; +import {FrameworkProvider, FrameworkProviderProps} from '@tryghost/admin-x-framework'; import {ZapierTemplate} from './components/settings/advanced/integrations/ZapierModal'; -interface AppProps { - ghostVersion: string; +interface AppProps extends Omit { officialThemes: OfficialTheme[]; zapierTemplates: ZapierTemplate[]; - externalNavigate: (link: ExternalLink) => void; - darkMode?: boolean; - unsplashConfig: DefaultHeaderTypes - sentryDSN: string | null; + darkMode: boolean; fetchKoenigLexical: FetchKoenigLexical; - onUpdate: (dataType: string, response: unknown) => void; - onInvalidate: (dataType: string) => void; - onDelete: (dataType: string, id: string) => void; upgradeStatus?: UpgradeStatusType; } -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - refetchOnWindowFocus: false, - staleTime: 5 * (60 * 1000), // 5 mins - cacheTime: 10 * (60 * 1000), // 10 mins - // We have custom retry logic for specific errors in fetchApi() - retry: false - } - } -}); - -function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate, darkMode = false, unsplashConfig, fetchKoenigLexical, sentryDSN, onUpdate, onInvalidate, onDelete, upgradeStatus}: AppProps) { +function App({officialThemes, zapierTemplates, upgradeStatus, darkMode, fetchKoenigLexical, ...props}: AppProps) { return ( - - - - - - - - - - - - - - - + + + + + + + + ); } diff --git a/apps/admin-x-settings/src/MainContent.tsx b/apps/admin-x-settings/src/MainContent.tsx index 201ed8b7eb..006abc2808 100644 --- a/apps/admin-x-settings/src/MainContent.tsx +++ b/apps/admin-x-settings/src/MainContent.tsx @@ -2,12 +2,12 @@ import ExitSettingsButton from './components/ExitSettingsButton'; import Settings from './components/Settings'; import Sidebar from './components/Sidebar'; import Users from './components/settings/general/Users'; -import useRouting from './hooks/useRouting'; import {Heading, topLevelBackdropClasses} from '@tryghost/admin-x-design-system'; import {ReactNode, useEffect} from 'react'; -import {canAccessSettings, isEditorUser} from './api/users'; +import {canAccessSettings, isEditorUser} from '@tryghost/admin-x-framework/api/users'; import {toast} from 'react-hot-toast'; import {useGlobalData} from './components/providers/GlobalDataProvider'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const Page: React.FC<{children: ReactNode}> = ({children}) => { return <> diff --git a/apps/admin-x-settings/src/api/slack.ts b/apps/admin-x-settings/src/api/slack.ts deleted file mode 100644 index ac2f14f5f6..0000000000 --- a/apps/admin-x-settings/src/api/slack.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {createMutation} from '../utils/api/hooks'; - -export const useTestSlack = createMutation({ - method: 'POST', - path: () => '/slack/test/' -}); diff --git a/apps/admin-x-settings/src/components/SearchableSection.tsx b/apps/admin-x-settings/src/components/SearchableSection.tsx index 8196ba0b57..e02eef7849 100644 --- a/apps/admin-x-settings/src/components/SearchableSection.tsx +++ b/apps/admin-x-settings/src/components/SearchableSection.tsx @@ -1,5 +1,5 @@ import {SettingSection, SettingSectionProps} from '@tryghost/admin-x-design-system'; -import {useSearch} from './providers/ServiceProvider'; +import {useSearch} from './providers/SettingsAppProvider'; const SearchableSection: React.FC & {keywords: string[]}> = ({keywords, ...props}) => { const {checkVisible} = useSearch(); diff --git a/apps/admin-x-settings/src/components/Sidebar.tsx b/apps/admin-x-settings/src/components/Sidebar.tsx index e35f3e85a3..2fd1e060c7 100644 --- a/apps/admin-x-settings/src/components/Sidebar.tsx +++ b/apps/admin-x-settings/src/components/Sidebar.tsx @@ -2,17 +2,17 @@ import GhostLogo from '../assets/images/orb-pink.png'; import React, {useEffect, useRef} from 'react'; import clsx from 'clsx'; import useFeatureFlag from '../hooks/useFeatureFlag'; -import useRouting from '../hooks/useRouting'; import {Button, Icon, SettingNavItem, SettingNavItemProps, SettingNavSection, TextField, useFocusContext} from '@tryghost/admin-x-design-system'; import {searchKeywords as advancedSearchKeywords} from './settings/advanced/AdvancedSettings'; import {searchKeywords as emailSearchKeywords} from './settings/email/EmailSettings'; import {searchKeywords as generalSearchKeywords} from './settings/general/GeneralSettings'; -import {getSettingValues} from '../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; import {searchKeywords as membershipSearchKeywords} from './settings/membership/MembershipSettings'; import {searchKeywords as siteSearchKeywords} from './settings/site/SiteSettings'; import {useGlobalData} from './providers/GlobalDataProvider'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; import {useScrollSectionContext, useScrollSectionNav} from '../hooks/useScrollSection'; -import {useSearch} from './providers/ServiceProvider'; +import {useSearch} from './providers/SettingsAppProvider'; const NavItem: React.FC & {keywords: string[]}> = ({keywords, navid, ...props}) => { const {ref, props: scrollProps} = useScrollSectionNav(navid); diff --git a/apps/admin-x-settings/src/components/TopLevelGroup.tsx b/apps/admin-x-settings/src/components/TopLevelGroup.tsx index aa7aa1ddb5..767fbecae1 100644 --- a/apps/admin-x-settings/src/components/TopLevelGroup.tsx +++ b/apps/admin-x-settings/src/components/TopLevelGroup.tsx @@ -1,8 +1,8 @@ import React, {useEffect, useState} from 'react'; -import useRouting from '../hooks/useRouting'; import {SettingGroup as Base, SettingGroupProps} from '@tryghost/admin-x-design-system'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; import {useScrollSection} from '../hooks/useScrollSection'; -import {useSearch} from './providers/ServiceProvider'; +import {useSearch} from './providers/SettingsAppProvider'; const TopLevelGroup: React.FC & {keywords: string[]}> = ({keywords, navid, ...props}) => { const {checkVisible} = useSearch(); diff --git a/apps/admin-x-settings/src/components/providers/GlobalDataProvider.tsx b/apps/admin-x-settings/src/components/providers/GlobalDataProvider.tsx index 2bfb5970b8..9678565810 100644 --- a/apps/admin-x-settings/src/components/providers/GlobalDataProvider.tsx +++ b/apps/admin-x-settings/src/components/providers/GlobalDataProvider.tsx @@ -1,9 +1,10 @@ import SpinningOrb from '../../assets/videos/logo-loader.mp4'; -import {Config, useBrowseConfig} from '../../api/config'; +import {Config, useBrowseConfig} from '@tryghost/admin-x-framework/api/config'; import {ReactNode, createContext, useContext} from 'react'; -import {Setting, useBrowseSettings} from '../../api/settings'; -import {SiteData, useBrowseSite} from '../../api/site'; -import {User, useCurrentUser} from '../../api/users'; +import {Setting, useBrowseSettings} from '@tryghost/admin-x-framework/api/settings'; +import {SiteData, useBrowseSite} from '@tryghost/admin-x-framework/api/site'; +import {User} from '@tryghost/admin-x-framework/api/users'; +import {useCurrentUser} from '@tryghost/admin-x-framework/api/currentUser'; interface GlobalData { settings: Setting[] diff --git a/apps/admin-x-settings/src/components/providers/ServiceProvider.tsx b/apps/admin-x-settings/src/components/providers/ServiceProvider.tsx deleted file mode 100644 index 8b1b541771..0000000000 --- a/apps/admin-x-settings/src/components/providers/ServiceProvider.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, {createContext, useContext} from 'react'; -import useSearchService, {SearchService} from '../../utils/search'; -import {DefaultHeaderTypes} from '../../unsplash/UnsplashTypes'; -import {UpgradeStatusType} from '../../utils/globalTypes'; -import {ZapierTemplate} from '../settings/advanced/integrations/ZapierModal'; - -export type ThemeVariant = { - category: string; - previewUrl: string; - image: string; -}; - -export type OfficialTheme = { - name: string; - category: string; - previewUrl: string; - ref: string; - image: string; - url?: string; - variants?: ThemeVariant[] -}; - -export type FetchKoenigLexical = () => Promise - -interface ServicesContextProps { - ghostVersion: string - officialThemes: OfficialTheme[]; - zapierTemplates: ZapierTemplate[]; - search: SearchService; - unsplashConfig: DefaultHeaderTypes; - sentryDSN: string | null; - onUpdate: (dataType: string, response: unknown) => void; - onInvalidate: (dataType: string) => void; - onDelete: (dataType: string, id: string) => void; - fetchKoenigLexical: FetchKoenigLexical; - upgradeStatus?: UpgradeStatusType; -} - -interface ServicesProviderProps { - children: React.ReactNode; - ghostVersion: string; - zapierTemplates: ZapierTemplate[]; - officialThemes: OfficialTheme[]; - unsplashConfig: DefaultHeaderTypes; - sentryDSN: string | null; - onUpdate: (dataType: string, response: unknown) => void; - onInvalidate: (dataType: string) => void; - onDelete: (dataType: string, id: string) => void; - fetchKoenigLexical: FetchKoenigLexical; - upgradeStatus?: UpgradeStatusType; -} - -const ServicesContext = createContext({ - ghostVersion: '', - officialThemes: [], - zapierTemplates: [], - search: {filter: '', setFilter: () => {}, checkVisible: () => true, highlightKeywords: () => ''}, - unsplashConfig: { - Authorization: '', - 'Accept-Version': '', - 'Content-Type': '', - 'App-Pragma': '', - 'X-Unsplash-Cache': true - }, - sentryDSN: null, - onUpdate: () => {}, - onInvalidate: () => {}, - onDelete: () => {}, - fetchKoenigLexical: async () => {}, - upgradeStatus: { - isRequired: false, - message: '' - } -}); - -const ServicesProvider: React.FC = ({children, ghostVersion, zapierTemplates, officialThemes, unsplashConfig, sentryDSN, onUpdate, onInvalidate, onDelete, fetchKoenigLexical, upgradeStatus}) => { - const search = useSearchService(); - - return ( - - {children} - - ); -}; - -export {ServicesContext, ServicesProvider}; - -export const useServices = () => useContext(ServicesContext); - -export const useOfficialThemes = () => useServices().officialThemes; - -export const useSearch = () => useServices().search; - -export const useSentryDSN = () => useServices().sentryDSN; - -export const useUpgradeStatus = () => useServices().upgradeStatus; diff --git a/apps/admin-x-settings/src/components/providers/SettingsAppProvider.tsx b/apps/admin-x-settings/src/components/providers/SettingsAppProvider.tsx new file mode 100644 index 0000000000..0eddfbe7b7 --- /dev/null +++ b/apps/admin-x-settings/src/components/providers/SettingsAppProvider.tsx @@ -0,0 +1,68 @@ +import GlobalDataProvider from './GlobalDataProvider'; +import useSearchService, {SearchService} from '../../utils/search'; +import {ReactNode, createContext, useContext} from 'react'; +import {ScrollSectionProvider} from '../../hooks/useScrollSection'; +import {ZapierTemplate} from '../settings/advanced/integrations/ZapierModal'; + +export type ThemeVariant = { + category: string; + previewUrl: string; + image: string; +}; + +export type OfficialTheme = { + name: string; + category: string; + previewUrl: string; + ref: string; + image: string; + url?: string; + variants?: ThemeVariant[] +}; + +export interface UpgradeStatusType { + isRequired: boolean; + message: string; +} + +interface SettingsAppContextType { + officialThemes: OfficialTheme[]; + zapierTemplates: ZapierTemplate[]; + search: SearchService; + upgradeStatus?: UpgradeStatusType; +} + +const SettingsAppContext = createContext({ + officialThemes: [], + zapierTemplates: [], + search: {filter: '', setFilter: () => {}, checkVisible: () => true, highlightKeywords: () => ''} +}); + +type SettingsAppProviderProps = Omit & {children: ReactNode}; + +const SettingsAppProvider: React.FC = ({children, ...props}) => { + const search = useSearchService(); + + return ( + + + + {children} + + + + ); +}; + +export default SettingsAppProvider; + +export const useSettingsApp = () => useContext(SettingsAppContext); + +export const useOfficialThemes = () => useSettingsApp().officialThemes; + +export const useSearch = () => useSettingsApp().search; + +export const useUpgradeStatus = () => useSettingsApp().upgradeStatus; diff --git a/apps/admin-x-settings/src/components/providers/SettingsRouter.tsx b/apps/admin-x-settings/src/components/providers/SettingsRouter.tsx new file mode 100644 index 0000000000..bff75bf0bd --- /dev/null +++ b/apps/admin-x-settings/src/components/providers/SettingsRouter.tsx @@ -0,0 +1,61 @@ +import React, {useEffect} from 'react'; +import {useRouteChangeCallback, useRouting} from '@tryghost/admin-x-framework/routing'; +import {useScrollSectionContext} from '../../hooks/useScrollSection'; +import type {ModalName} from './routing/modals'; + +export const modalPaths: {[key: string]: ModalName} = { + 'design/change-theme': 'DesignAndThemeModal', + 'design/edit': 'DesignAndThemeModal', + // this is a special route, because it can install a theme directly from the Ghost Marketplace + 'design/change-theme/install': 'DesignAndThemeModal', + 'navigation/edit': 'NavigationModal', + 'staff/invite': 'InviteUserModal', + 'staff/:slug': 'UserDetailModal', + 'portal/edit': 'PortalModal', + 'tiers/add': 'TierDetailModal', + 'tiers/:id': 'TierDetailModal', + 'stripe-connect': 'StripeConnectModal', + 'newsletters/new': 'AddNewsletterModal', + 'newsletters/:id': 'NewsletterDetailModal', + 'history/view': 'HistoryModal', + 'history/view/:user': 'HistoryModal', + 'integrations/zapier': 'ZapierModal', + 'integrations/slack': 'SlackModal', + 'integrations/amp': 'AmpModal', + 'integrations/unsplash': 'UnsplashModal', + 'integrations/firstpromoter': 'FirstpromoterModal', + 'integrations/pintura': 'PinturaModal', + 'integrations/new': 'AddIntegrationModal', + 'integrations/:id': 'CustomIntegrationModal', + 'recommendations/add': 'AddRecommendationModal', + 'recommendations/edit': 'EditRecommendationModal', + 'announcement-bar/edit': 'AnnouncementBarModal', + 'embed-signup-form/show': 'EmbedSignupFormModal', + 'offers/edit': 'OffersModal', + 'offers/new': 'AddOfferModal', + 'offers/:id': 'EditOfferModal', + about: 'AboutModal' +}; + +export const loadModals = () => import('./routing/modals'); + +const SettingsRouter: React.FC = () => { + const {updateNavigatedSection, scrollToSection} = useScrollSectionContext(); + const {route} = useRouting(); + + useRouteChangeCallback((newPath, oldPath) => { + if (newPath === oldPath) { + scrollToSection(newPath.split('/')[0]); + } + }, [scrollToSection]); + + useEffect(() => { + if (route !== undefined) { + updateNavigatedSection(route.split('/')[0]); + } + }, [route, updateNavigatedSection]); + + return null; +}; + +export default SettingsRouter; diff --git a/apps/admin-x-settings/src/components/providers/routing/modals.tsx b/apps/admin-x-settings/src/components/providers/routing/modals.tsx index 6d944b760e..1543afe2ff 100644 --- a/apps/admin-x-settings/src/components/providers/routing/modals.tsx +++ b/apps/admin-x-settings/src/components/providers/routing/modals.tsx @@ -1,5 +1,5 @@ import type {NiceModalHocProps} from '@ebay/nice-modal-react'; -import type {RoutingModalProps} from '../RoutingProvider'; +import type {RoutingModalProps} from '@tryghost/admin-x-framework/routing'; import AboutModal from '../../settings/general/About'; import AddIntegrationModal from '../../settings/advanced/integrations/AddIntegrationModal'; diff --git a/apps/admin-x-settings/src/components/settings/advanced/CodeInjection.tsx b/apps/admin-x-settings/src/components/settings/advanced/CodeInjection.tsx index 3ce32cf180..335a7c8e59 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/CodeInjection.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/CodeInjection.tsx @@ -5,7 +5,7 @@ import TopLevelGroup from '../../TopLevelGroup'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {Button, CodeEditor, TabView, withErrorBoundary} from '@tryghost/admin-x-design-system'; import {ReactCodeMirrorRef} from '@uiw/react-codemirror'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; const CodeInjection: React.FC<{ keywords: string[] }> = ({keywords}) => { const { diff --git a/apps/admin-x-settings/src/components/settings/advanced/History.tsx b/apps/admin-x-settings/src/components/settings/advanced/History.tsx index 2fe4046cc1..7f33ae1354 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/History.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/History.tsx @@ -1,7 +1,7 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useRouting from '../../../hooks/useRouting'; import {Button, withErrorBoundary} from '@tryghost/admin-x-design-system'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const History: React.FC<{ keywords: string[] }> = ({keywords}) => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/HistoryModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/HistoryModal.tsx index 5c01a522a0..38aa204c31 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/HistoryModal.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/HistoryModal.tsx @@ -1,12 +1,11 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react'; -import useFilterableApi from '../../../hooks/useFilterableApi'; -import useRouting from '../../../hooks/useRouting'; -import {Action, getActionTitle, getContextResource, getLinkTarget, isBulkAction, useBrowseActions} from '../../../api/actions'; +import {Action, getActionTitle, getContextResource, getLinkTarget, isBulkAction, useBrowseActions} from '@tryghost/admin-x-framework/api/actions'; import {Avatar, Button, Icon, InfiniteScrollListener, List, ListItem, LoadSelectOptions, Modal, NoValueLabel, Popover, Select, SelectOption, Toggle, ToggleGroup, debounce} from '@tryghost/admin-x-design-system'; -import {RoutingModalProps} from '../../providers/RoutingProvider'; -import {User} from '../../../api/users'; +import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; +import {User} from '@tryghost/admin-x-framework/api/users'; import {generateAvatarColor, getInitials} from '../../../utils/helpers'; import {useCallback, useState} from 'react'; +import {useFilterableApi} from '@tryghost/admin-x-framework/hooks'; const HistoryIcon: React.FC<{action: Action}> = ({action}) => { let name = 'pen'; diff --git a/apps/admin-x-settings/src/components/settings/advanced/Integrations.tsx b/apps/admin-x-settings/src/components/settings/advanced/Integrations.tsx index ef6c973fa2..d1aecd0c57 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/Integrations.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/Integrations.tsx @@ -1,19 +1,19 @@ import NiceModal from '@ebay/nice-modal-react'; import React, {useState} from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useHandleError from '../../../utils/api/handleError'; import usePinturaEditor from '../../../hooks/usePinturaEditor'; -import useRouting from '../../../hooks/useRouting'; import {ReactComponent as AmpIcon} from '../../../assets/icons/amp.svg'; import {Button, ConfirmationModal, Icon, List, ListItem, NoValueLabel, TabView, showToast, withErrorBoundary} from '@tryghost/admin-x-design-system'; import {ReactComponent as FirstPromoterIcon} from '../../../assets/icons/firstpromoter.svg'; -import {Integration, useBrowseIntegrations, useDeleteIntegration} from '../../../api/integrations'; +import {Integration, useBrowseIntegrations, useDeleteIntegration} from '@tryghost/admin-x-framework/api/integrations'; import {ReactComponent as PinturaIcon} from '../../../assets/icons/pintura.svg'; import {ReactComponent as SlackIcon} from '../../../assets/icons/slack.svg'; import {ReactComponent as UnsplashIcon} from '../../../assets/icons/unsplash.svg'; import {ReactComponent as ZapierIcon} from '../../../assets/icons/zapier.svg'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; import {useGlobalData} from '../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; interface IntegrationItemProps { icon?: React.ReactNode, diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/AddIntegrationModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/AddIntegrationModal.tsx index 0b2df7e2c3..a23610576c 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/integrations/AddIntegrationModal.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/AddIntegrationModal.tsx @@ -1,11 +1,10 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React, {useEffect, useState} from 'react'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import {Form, LimitModal, Modal, TextField} from '@tryghost/admin-x-design-system'; import {HostLimitError, useLimiter} from '../../../../hooks/useLimiter'; -import {RoutingModalProps} from '../../../providers/RoutingProvider'; -import {useCreateIntegration} from '../../../../api/integrations'; +import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; +import {useCreateIntegration} from '@tryghost/admin-x-framework/api/integrations'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const AddIntegrationModal: React.FC = () => { const modal = useModal(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/AmpModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/AmpModal.tsx index c9bf7d1346..c90b8ade5a 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/integrations/AmpModal.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/AmpModal.tsx @@ -1,12 +1,12 @@ import IntegrationHeader from './IntegrationHeader'; import NiceModal from '@ebay/nice-modal-react'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import {Form, Modal, TextField, Toggle} from '@tryghost/admin-x-design-system'; import {ReactComponent as Icon} from '../../../../assets/icons/amp.svg'; -import {Setting, getSettingValues, useEditSettings} from '../../../../api/settings'; +import {Setting, getSettingValues, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; import {useEffect, useState} from 'react'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const AmpModal = NiceModal.create(() => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/CustomIntegrationModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/CustomIntegrationModal.tsx index 44354b593e..7408459645 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/integrations/CustomIntegrationModal.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/CustomIntegrationModal.tsx @@ -3,15 +3,14 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React, {useEffect, useState} from 'react'; import WebhooksTable from './WebhooksTable'; import useForm from '../../../../hooks/useForm'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; -import {APIKey, useRefreshAPIKey} from '../../../../api/apiKeys'; +import {APIKey, useRefreshAPIKey} from '@tryghost/admin-x-framework/api/apiKeys'; import {ConfirmationModal, Form, ImageUpload, Modal, TextField, showToast} from '@tryghost/admin-x-design-system'; -import {Integration, useBrowseIntegrations, useEditIntegration} from '../../../../api/integrations'; -import {RoutingModalProps} from '../../../providers/RoutingProvider'; -import {getGhostPaths} from '../../../../utils/helpers'; -import {getImageUrl, useUploadImage} from '../../../../api/images'; +import {Integration, useBrowseIntegrations, useEditIntegration} from '@tryghost/admin-x-framework/api/integrations'; +import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; +import {getGhostPaths} from '@tryghost/admin-x-framework/helpers'; +import {getImageUrl, useUploadImage} from '@tryghost/admin-x-framework/api/images'; import {toast} from 'react-hot-toast'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const CustomIntegrationModalContent: React.FC<{integration: Integration}> = ({integration}) => { const modal = useModal(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/FirstPromoterModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/FirstPromoterModal.tsx index e373930a6b..c5410ec854 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/integrations/FirstPromoterModal.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/FirstPromoterModal.tsx @@ -1,12 +1,12 @@ import IntegrationHeader from './IntegrationHeader'; import NiceModal from '@ebay/nice-modal-react'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import {Form, Modal, TextField, Toggle} from '@tryghost/admin-x-design-system'; import {ReactComponent as Icon} from '../../../../assets/icons/firstpromoter.svg'; -import {Setting, getSettingValues, useEditSettings} from '../../../../api/settings'; +import {Setting, getSettingValues, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; import {useEffect, useState} from 'react'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const FirstpromoterModal = NiceModal.create(() => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/PinturaModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/PinturaModal.tsx index 1a6a85a89a..91aeb61043 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/integrations/PinturaModal.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/PinturaModal.tsx @@ -1,14 +1,14 @@ import IntegrationHeader from './IntegrationHeader'; import NiceModal from '@ebay/nice-modal-react'; import pinturaScreenshot from '../../../../assets/images/pintura-screenshot.png'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import {Button, Form, Modal, Toggle, showToast} from '@tryghost/admin-x-design-system'; import {ReactComponent as Icon} from '../../../../assets/icons/pintura.svg'; -import {Setting, getSettingValues, useEditSettings} from '../../../../api/settings'; +import {Setting, getSettingValues, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; import {useEffect, useRef, useState} from 'react'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; -import {useUploadFile} from '../../../../api/files'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; +import {useUploadFile} from '@tryghost/admin-x-framework/api/files'; const PinturaModal = NiceModal.create(() => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/SlackModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/SlackModal.tsx index 53c12a7937..6ff6d60860 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/integrations/SlackModal.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/SlackModal.tsx @@ -1,13 +1,12 @@ import IntegrationHeader from './IntegrationHeader'; import NiceModal from '@ebay/nice-modal-react'; import toast from 'react-hot-toast'; -import useRouting from '../../../../hooks/useRouting'; import useSettingGroup from '../../../../hooks/useSettingGroup'; import validator from 'validator'; import {Button, Form, Modal, TextField, showToast} from '@tryghost/admin-x-design-system'; import {ReactComponent as Icon} from '../../../../assets/icons/slack.svg'; -import {getSettingValues} from '../../../../api/settings'; -import {useTestSlack} from '../../../../api/slack'; +import {getSettingValues, useTestSlack} from '@tryghost/admin-x-framework/api/settings'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const SlackModal = NiceModal.create(() => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/UnsplashModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/UnsplashModal.tsx index ba94a0574e..f148162169 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/integrations/UnsplashModal.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/UnsplashModal.tsx @@ -1,11 +1,11 @@ import IntegrationHeader from './IntegrationHeader'; import NiceModal from '@ebay/nice-modal-react'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import {Form, Modal, Toggle} from '@tryghost/admin-x-design-system'; import {ReactComponent as Icon} from '../../../../assets/icons/unsplash.svg'; -import {Setting, getSettingValues, useEditSettings} from '../../../../api/settings'; +import {Setting, getSettingValues, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const UnsplashModal = NiceModal.create(() => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhookModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhookModal.tsx index cc379ba43a..8dd18faf96 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhookModal.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhookModal.tsx @@ -2,11 +2,11 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React from 'react'; import toast from 'react-hot-toast'; import useForm from '../../../../hooks/useForm'; -import useHandleError from '../../../../utils/api/handleError'; import validator from 'validator'; import webhookEventOptions from './webhookEventOptions'; import {Form, Modal, Select, TextField, showToast} from '@tryghost/admin-x-design-system'; -import {Webhook, useCreateWebhook, useEditWebhook} from '../../../../api/webhooks'; +import {Webhook, useCreateWebhook, useEditWebhook} from '@tryghost/admin-x-framework/api/webhooks'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; interface WebhookModalProps { webhook?: Webhook; diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhooksTable.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhooksTable.tsx index a5900af75b..83f03a502d 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhooksTable.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhooksTable.tsx @@ -1,10 +1,10 @@ import NiceModal from '@ebay/nice-modal-react'; import WebhookModal from './WebhookModal'; -import useHandleError from '../../../../utils/api/handleError'; import {Button, ConfirmationModal, Table, TableCell, TableHead, TableRow, showToast} from '@tryghost/admin-x-design-system'; -import {Integration} from '../../../../api/integrations'; +import {Integration} from '@tryghost/admin-x-framework/api/integrations'; import {getWebhookEventLabel} from './webhookEventOptions'; -import {useDeleteWebhook} from '../../../../api/webhooks'; +import {useDeleteWebhook} from '@tryghost/admin-x-framework/api/webhooks'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const WebhooksTable: React.FC<{integration: Integration}> = ({integration}) => { const {mutateAsync: deleteWebhook} = useDeleteWebhook(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/ZapierModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/ZapierModal.tsx index 8f75ac9327..c0af89553a 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/integrations/ZapierModal.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/ZapierModal.tsx @@ -1,17 +1,18 @@ import APIKeys from './APIKeys'; import IntegrationHeader from './IntegrationHeader'; import NiceModal from '@ebay/nice-modal-react'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import {Button, ConfirmationModal, Icon, List, ListItem, Modal} from '@tryghost/admin-x-design-system'; import {ReactComponent as Logo} from '../../../../assets/images/zapier-logo.svg'; import {ReactComponent as ZapierIcon} from '../../../../assets/icons/zapier.svg'; -import {getGhostPaths, resolveAsset} from '../../../../utils/helpers'; -import {useBrowseIntegrations} from '../../../../api/integrations'; +import {getGhostPaths} from '@tryghost/admin-x-framework/helpers'; +import {resolveAsset} from '../../../../utils/helpers'; +import {useBrowseIntegrations} from '@tryghost/admin-x-framework/api/integrations'; import {useEffect, useState} from 'react'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; -import {useRefreshAPIKey} from '../../../../api/apiKeys'; -import {useServices} from '../../../providers/ServiceProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRefreshAPIKey} from '@tryghost/admin-x-framework/api/apiKeys'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; +import {useSettingsApp} from '../../../providers/SettingsAppProvider'; export interface ZapierTemplate { ghostImage: string; @@ -23,7 +24,7 @@ export interface ZapierTemplate { const ZapierModal = NiceModal.create(() => { const modal = NiceModal.useModal(); const {updateRoute} = useRouting(); - const {zapierTemplates} = useServices(); + const {zapierTemplates} = useSettingsApp(); const {data: {integrations} = {integrations: []}} = useBrowseIntegrations(); const {config} = useGlobalData(); const {adminRoot} = getGhostPaths(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx b/apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx index f5828476e3..17008d14c9 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx @@ -1,11 +1,11 @@ import FeatureToggle from './FeatureToggle'; import LabItem from './LabItem'; import React, {useState} from 'react'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import {Button, FileUpload, List, showToast} from '@tryghost/admin-x-design-system'; -import {downloadRedirects, useUploadRedirects} from '../../../../api/redirects'; -import {downloadRoutes, useUploadRoutes} from '../../../../api/routes'; +import {downloadRedirects, useUploadRedirects} from '@tryghost/admin-x-framework/api/redirects'; +import {downloadRoutes, useUploadRoutes} from '@tryghost/admin-x-framework/api/routes'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const BetaFeatures: React.FC = () => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/advanced/labs/FeatureToggle.tsx b/apps/admin-x-settings/src/components/settings/advanced/labs/FeatureToggle.tsx index 6a80881e70..6835d89916 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/labs/FeatureToggle.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/labs/FeatureToggle.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import useHandleError from '../../../../utils/api/handleError'; -import {ConfigResponseType, configDataType} from '../../../../api/config'; +import {ConfigResponseType, configDataType} from '@tryghost/admin-x-framework/api/config'; import {Toggle} from '@tryghost/admin-x-design-system'; -import {getSettingValue, useEditSettings} from '../../../../api/settings'; +import {getSettingValue, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; import {useQueryClient} from '@tanstack/react-query'; const FeatureToggle: React.FC<{ flag: string; label?: string; }> = ({label, flag}) => { diff --git a/apps/admin-x-settings/src/components/settings/advanced/labs/MigrationOptions.tsx b/apps/admin-x-settings/src/components/settings/advanced/labs/MigrationOptions.tsx index 984d5b55d1..80aa9b0d7a 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/labs/MigrationOptions.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/labs/MigrationOptions.tsx @@ -1,9 +1,9 @@ import LabItem from './LabItem'; import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React, {useState} from 'react'; -import useHandleError from '../../../../utils/api/handleError'; import {Button, ConfirmationModal, FileUpload, List, showToast} from '@tryghost/admin-x-design-system'; -import {downloadAllContent, useDeleteAllContent, useImportContent} from '../../../../api/db'; +import {downloadAllContent, useDeleteAllContent, useImportContent} from '@tryghost/admin-x-framework/api/db'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; import {useQueryClient} from '@tanstack/react-query'; const ImportModalContent = () => { diff --git a/apps/admin-x-settings/src/components/settings/email/DefaultRecipients.tsx b/apps/admin-x-settings/src/components/settings/email/DefaultRecipients.tsx index 5863ce3d8e..80850fe5ec 100644 --- a/apps/admin-x-settings/src/components/settings/email/DefaultRecipients.tsx +++ b/apps/admin-x-settings/src/components/settings/email/DefaultRecipients.tsx @@ -5,7 +5,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup'; import {MultiSelect, MultiSelectOption, Select, SettingGroupContent, withErrorBoundary} from '@tryghost/admin-x-design-system'; import {MultiValue} from 'react-select'; import {getOptionLabel} from '../../../utils/helpers'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; type RefipientValueArgs = { defaultEmailRecipients: string; diff --git a/apps/admin-x-settings/src/components/settings/email/EmailSettings.tsx b/apps/admin-x-settings/src/components/settings/email/EmailSettings.tsx index 946c111880..ce1fc86931 100644 --- a/apps/admin-x-settings/src/components/settings/email/EmailSettings.tsx +++ b/apps/admin-x-settings/src/components/settings/email/EmailSettings.tsx @@ -4,7 +4,7 @@ import MailGun from './Mailgun'; import Newsletters from './Newsletters'; import React from 'react'; import SearchableSection from '../../SearchableSection'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; import {useGlobalData} from '../../providers/GlobalDataProvider'; export const searchKeywords = { diff --git a/apps/admin-x-settings/src/components/settings/email/EnableNewsletters.tsx b/apps/admin-x-settings/src/components/settings/email/EnableNewsletters.tsx index 532120a46a..15066e23f3 100644 --- a/apps/admin-x-settings/src/components/settings/email/EnableNewsletters.tsx +++ b/apps/admin-x-settings/src/components/settings/email/EnableNewsletters.tsx @@ -1,10 +1,10 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useHandleError from '../../../utils/api/handleError'; -import useRouting from '../../../hooks/useRouting'; import {Banner, Icon, SettingGroupContent, Toggle, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {Setting, getSettingValues, useEditSettings} from '../../../api/settings'; +import {Setting, getSettingValues, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; import {useGlobalData} from '../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const EnableNewsletters: React.FC<{ keywords: string[] }> = ({keywords}) => { const {settings} = useGlobalData(); diff --git a/apps/admin-x-settings/src/components/settings/email/Mailgun.tsx b/apps/admin-x-settings/src/components/settings/email/Mailgun.tsx index 91a6b8643e..990ec55c22 100644 --- a/apps/admin-x-settings/src/components/settings/email/Mailgun.tsx +++ b/apps/admin-x-settings/src/components/settings/email/Mailgun.tsx @@ -1,9 +1,9 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useHandleError from '../../../utils/api/handleError'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {IconLabel, Link, Select, SettingGroupContent, TextField, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {getSettingValues, useEditSettings} from '../../../api/settings'; +import {getSettingValues, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const MAILGUN_REGIONS = [ {label: '🇺🇸 US', value: 'https://api.mailgun.net/v3'}, diff --git a/apps/admin-x-settings/src/components/settings/email/Newsletters.tsx b/apps/admin-x-settings/src/components/settings/email/Newsletters.tsx index d648686ce0..9a8d69bef4 100644 --- a/apps/admin-x-settings/src/components/settings/email/Newsletters.tsx +++ b/apps/admin-x-settings/src/components/settings/email/Newsletters.tsx @@ -2,14 +2,14 @@ import NewslettersList from './newsletters/NewslettersList'; import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React, {ReactNode, useEffect, useState} from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useHandleError from '../../../utils/api/handleError'; import useQueryParams from '../../../hooks/useQueryParams'; -import useRouting from '../../../hooks/useRouting'; -import {APIError} from '../../../utils/errors'; +import {APIError} from '@tryghost/admin-x-framework/errors'; import {Button, ConfirmationModal, TabView, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {InfiniteData, useQueryClient} from '@tanstack/react-query'; -import {Newsletter, NewslettersResponseType, newslettersDataType, useBrowseNewsletters, useEditNewsletter, useVerifyNewsletterEmail} from '../../../api/newsletters'; +import {InfiniteData, useQueryClient} from '@tryghost/admin-x-framework'; +import {Newsletter, NewslettersResponseType, newslettersDataType, useBrowseNewsletters, useEditNewsletter, useVerifyNewsletterEmail} from '@tryghost/admin-x-framework/api/newsletters'; import {arrayMove} from '@dnd-kit/sortable'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const NavigateToNewsletter = ({id, children}: {id: string; children: ReactNode}) => { const modal = useModal(); diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/AddNewsletterModal.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/AddNewsletterModal.tsx index fb50fba4af..89064bb64c 100644 --- a/apps/admin-x-settings/src/components/settings/email/newsletters/AddNewsletterModal.tsx +++ b/apps/admin-x-settings/src/components/settings/email/newsletters/AddNewsletterModal.tsx @@ -1,14 +1,13 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React, {useEffect} from 'react'; import useForm from '../../../../hooks/useForm'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import {Form, LimitModal, Modal, TextArea, TextField, Toggle, showToast} from '@tryghost/admin-x-design-system'; import {HostLimitError, useLimiter} from '../../../../hooks/useLimiter'; -import {RoutingModalProps} from '../../../providers/RoutingProvider'; +import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; import {toast} from 'react-hot-toast'; -import {useAddNewsletter} from '../../../../api/newsletters'; -import {useBrowseMembers} from '../../../../api/members'; +import {useAddNewsletter} from '@tryghost/admin-x-framework/api/newsletters'; +import {useBrowseMembers} from '@tryghost/admin-x-framework/api/members'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const AddNewsletterModal: React.FC = () => { const modal = useModal(); diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx index c756ad27e8..2138ec181d 100644 --- a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx +++ b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx @@ -3,19 +3,18 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React, {useEffect, useState} from 'react'; import useFeatureFlag from '../../../../hooks/useFeatureFlag'; import useForm, {ErrorMessages} from '../../../../hooks/useForm'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import useSettingGroup from '../../../../hooks/useSettingGroup'; import validator from 'validator'; import {Button, ButtonGroup, ColorPickerField, ConfirmationModal, Form, Heading, Hint, HtmlField, Icon, ImageUpload, LimitModal, PreviewModalContent, Select, SelectOption, Separator, Tab, TabView, TextArea, TextField, Toggle, ToggleGroup, showToast} from '@tryghost/admin-x-design-system'; import {HostLimitError, useLimiter} from '../../../../hooks/useLimiter'; -import {Newsletter, useBrowseNewsletters, useEditNewsletter} from '../../../../api/newsletters'; -import {RoutingModalProps} from '../../../providers/RoutingProvider'; -import {fullEmailAddress} from '../../../../api/site'; -import {getImageUrl, useUploadImage} from '../../../../api/images'; -import {getSettingValues} from '../../../../api/settings'; +import {Newsletter, useBrowseNewsletters, useEditNewsletter} from '@tryghost/admin-x-framework/api/newsletters'; +import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; +import {fullEmailAddress} from '@tryghost/admin-x-framework/api/site'; +import {getImageUrl, useUploadImage} from '@tryghost/admin-x-framework/api/images'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; import {textColorForBackgroundColor} from '@tryghost/color-utils'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const Sidebar: React.FC<{ newsletter: Newsletter; diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx index 5e29467872..a4cf9cb2d3 100644 --- a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx +++ b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx @@ -1,9 +1,9 @@ import NewsletterPreviewContent from './NewsletterPreviewContent'; import React from 'react'; import useFeatureFlag from '../../../../hooks/useFeatureFlag'; -import {Newsletter} from '../../../../api/newsletters'; -import {fullEmailAddress} from '../../../../api/site'; -import {getSettingValues} from '../../../../api/settings'; +import {Newsletter} from '@tryghost/admin-x-framework/api/newsletters'; +import {fullEmailAddress} from '@tryghost/admin-x-framework/api/site'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; import {textColorForBackgroundColor} from '@tryghost/color-utils'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/NewslettersList.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/NewslettersList.tsx index b64af3fbe4..7776fd528a 100644 --- a/apps/admin-x-settings/src/components/settings/email/newsletters/NewslettersList.tsx +++ b/apps/admin-x-settings/src/components/settings/email/newsletters/NewslettersList.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import useRouting from '../../../../hooks/useRouting'; import {Button, DragIndicator, NoValueLabel, SortableItemContainerProps, SortableList, Table, TableCell, TableRow} from '@tryghost/admin-x-design-system'; -import {Newsletter} from '../../../../api/newsletters'; +import {Newsletter} from '@tryghost/admin-x-framework/api/newsletters'; import {numberWithCommas} from '../../../../utils/helpers'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; interface NewslettersListProps { newsletters: Newsletter[]; diff --git a/apps/admin-x-settings/src/components/settings/email/useDefaultRecipientsOptions.tsx b/apps/admin-x-settings/src/components/settings/email/useDefaultRecipientsOptions.tsx index 7bcfc794c6..731b8dc645 100644 --- a/apps/admin-x-settings/src/components/settings/email/useDefaultRecipientsOptions.tsx +++ b/apps/admin-x-settings/src/components/settings/email/useDefaultRecipientsOptions.tsx @@ -1,11 +1,11 @@ -import useFilterableApi from '../../../hooks/useFilterableApi'; import {GroupBase, MultiValue} from 'react-select'; -import {Label} from '../../../api/labels'; +import {Label} from '@tryghost/admin-x-framework/api/labels'; import {LoadMultiSelectOptions, MultiSelectOption, debounce} from '@tryghost/admin-x-design-system'; -import {Offer} from '../../../api/offers'; -import {Tier} from '../../../api/tiers'; +import {Offer} from '@tryghost/admin-x-framework/api/offers'; +import {Tier} from '@tryghost/admin-x-framework/api/tiers'; import {isObjectId} from '../../../utils/helpers'; import {useEffect, useState} from 'react'; +import {useFilterableApi} from '@tryghost/admin-x-framework/hooks'; const SIMPLE_SEGMENT_OPTIONS: MultiSelectOption[] = [{ label: 'Free members', diff --git a/apps/admin-x-settings/src/components/settings/general/About.tsx b/apps/admin-x-settings/src/components/settings/general/About.tsx index 8ce8872b33..24b0d694e5 100644 --- a/apps/admin-x-settings/src/components/settings/general/About.tsx +++ b/apps/admin-x-settings/src/components/settings/general/About.tsx @@ -1,11 +1,10 @@ import NiceModal from '@ebay/nice-modal-react'; -import useRouting from '../../../hooks/useRouting'; import {GhostLogo, Icon, Modal, Separator} from '@tryghost/admin-x-design-system'; -import {RoutingModalProps} from '../../providers/RoutingProvider'; +import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; import {linkToGitHubReleases} from '../../../utils/linkToGithubReleases'; import {showDatabaseWarning} from '../../../utils/showDatabaseWarning'; import {useGlobalData} from '../../providers/GlobalDataProvider'; -import {useUpgradeStatus} from '../../providers/ServiceProvider'; +import {useUpgradeStatus} from '../../providers/SettingsAppProvider'; const AboutModal = NiceModal.create(({}) => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/general/Facebook.tsx b/apps/admin-x-settings/src/components/settings/general/Facebook.tsx index 75227ce9c9..0bcb7c008a 100644 --- a/apps/admin-x-settings/src/components/settings/general/Facebook.tsx +++ b/apps/admin-x-settings/src/components/settings/general/Facebook.tsx @@ -1,11 +1,11 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useHandleError from '../../../utils/api/handleError'; import usePinturaEditor from '../../../hooks/usePinturaEditor'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {FacebookLogo, ImageUpload, SettingGroupContent, TextField, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {getImageUrl, useUploadImage} from '../../../api/images'; -import {getSettingValues} from '../../../api/settings'; +import {getImageUrl, useUploadImage} from '@tryghost/admin-x-framework/api/images'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const Facebook: React.FC<{ keywords: string[] }> = ({keywords}) => { const { diff --git a/apps/admin-x-settings/src/components/settings/general/InviteUserModal.tsx b/apps/admin-x-settings/src/components/settings/general/InviteUserModal.tsx index 88d73ae488..5ca43c51a2 100644 --- a/apps/admin-x-settings/src/components/settings/general/InviteUserModal.tsx +++ b/apps/admin-x-settings/src/components/settings/general/InviteUserModal.tsx @@ -1,13 +1,13 @@ import NiceModal from '@ebay/nice-modal-react'; -import useHandleError from '../../../utils/api/handleError'; -import useRouting from '../../../hooks/useRouting'; import validator from 'validator'; import {HostLimitError, useLimiter} from '../../../hooks/useLimiter'; import {Modal, Radio, TextField, showToast} from '@tryghost/admin-x-design-system'; -import {useAddInvite, useBrowseInvites} from '../../../api/invites'; -import {useBrowseRoles} from '../../../api/roles'; -import {useBrowseUsers} from '../../../api/users'; +import {useAddInvite, useBrowseInvites} from '@tryghost/admin-x-framework/api/invites'; +import {useBrowseRoles} from '@tryghost/admin-x-framework/api/roles'; +import {useBrowseUsers} from '@tryghost/admin-x-framework/api/users'; import {useEffect, useRef, useState} from 'react'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; type RoleType = 'administrator' | 'editor' | 'author' | 'contributor'; diff --git a/apps/admin-x-settings/src/components/settings/general/LockSite.tsx b/apps/admin-x-settings/src/components/settings/general/LockSite.tsx index 927fc7e93a..70f1c03657 100644 --- a/apps/admin-x-settings/src/components/settings/general/LockSite.tsx +++ b/apps/admin-x-settings/src/components/settings/general/LockSite.tsx @@ -2,7 +2,7 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {Icon, Link, SettingGroupContent, TextField, Toggle, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; const LockSite: React.FC<{ keywords: string[] }> = ({keywords}) => { const { diff --git a/apps/admin-x-settings/src/components/settings/general/Metadata.tsx b/apps/admin-x-settings/src/components/settings/general/Metadata.tsx index d9547ab336..3d61b47b86 100644 --- a/apps/admin-x-settings/src/components/settings/general/Metadata.tsx +++ b/apps/admin-x-settings/src/components/settings/general/Metadata.tsx @@ -2,7 +2,7 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {GoogleLogo, Heading, Icon, SettingGroupContent, TextField, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; interface SearchEnginePreviewProps { title: string; diff --git a/apps/admin-x-settings/src/components/settings/general/PublicationLanguage.tsx b/apps/admin-x-settings/src/components/settings/general/PublicationLanguage.tsx index 0759e28388..fb169a627c 100644 --- a/apps/admin-x-settings/src/components/settings/general/PublicationLanguage.tsx +++ b/apps/admin-x-settings/src/components/settings/general/PublicationLanguage.tsx @@ -2,7 +2,7 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {SettingGroupContent, TextField, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; const PublicationLanguage: React.FC<{ keywords: string[] }> = ({keywords}) => { const { diff --git a/apps/admin-x-settings/src/components/settings/general/SocialAccounts.tsx b/apps/admin-x-settings/src/components/settings/general/SocialAccounts.tsx index 5f13d55a06..37a0a05780 100644 --- a/apps/admin-x-settings/src/components/settings/general/SocialAccounts.tsx +++ b/apps/admin-x-settings/src/components/settings/general/SocialAccounts.tsx @@ -3,7 +3,7 @@ import TopLevelGroup from '../../TopLevelGroup'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {SettingGroupContent, TextField, withErrorBoundary} from '@tryghost/admin-x-design-system'; import {facebookHandleToUrl, facebookUrlToHandle, twitterHandleToUrl, twitterUrlToHandle, validateFacebookUrl, validateTwitterUrl} from '../../../utils/socialUrls'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; const SocialAccounts: React.FC<{ keywords: string[] }> = ({keywords}) => { const { diff --git a/apps/admin-x-settings/src/components/settings/general/TimeZone.tsx b/apps/admin-x-settings/src/components/settings/general/TimeZone.tsx index aab2ab757f..a1d79c9107 100644 --- a/apps/admin-x-settings/src/components/settings/general/TimeZone.tsx +++ b/apps/admin-x-settings/src/components/settings/general/TimeZone.tsx @@ -4,7 +4,7 @@ import timezoneData from '@tryghost/timezone-data'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {Select, SettingGroupContent, withErrorBoundary} from '@tryghost/admin-x-design-system'; import {getLocalTime} from '../../../utils/helpers'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; interface TimezoneDataDropdownOption { name: string; diff --git a/apps/admin-x-settings/src/components/settings/general/TitleAndDescription.tsx b/apps/admin-x-settings/src/components/settings/general/TitleAndDescription.tsx index a623e46bcd..284fe84649 100644 --- a/apps/admin-x-settings/src/components/settings/general/TitleAndDescription.tsx +++ b/apps/admin-x-settings/src/components/settings/general/TitleAndDescription.tsx @@ -2,7 +2,7 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {SettingGroupContent, TextField, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; const TitleAndDescription: React.FC<{ keywords: string[] }> = ({keywords}) => { const { diff --git a/apps/admin-x-settings/src/components/settings/general/Twitter.tsx b/apps/admin-x-settings/src/components/settings/general/Twitter.tsx index 914ea756a5..d3f0e21b93 100644 --- a/apps/admin-x-settings/src/components/settings/general/Twitter.tsx +++ b/apps/admin-x-settings/src/components/settings/general/Twitter.tsx @@ -1,11 +1,11 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useHandleError from '../../../utils/api/handleError'; import usePinturaEditor from '../../../hooks/usePinturaEditor'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {ImageUpload, SettingGroupContent, TextField, TwitterLogo, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {getImageUrl, useUploadImage} from '../../../api/images'; -import {getSettingValues} from '../../../api/settings'; +import {getImageUrl, useUploadImage} from '@tryghost/admin-x-framework/api/images'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const Twitter: React.FC<{ keywords: string[] }> = ({keywords}) => { const { diff --git a/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx b/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx index 9a06c2ae81..85f384f767 100644 --- a/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx +++ b/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx @@ -7,18 +7,17 @@ import React, {useCallback, useEffect} from 'react'; import StaffToken from './users/StaffToken'; import clsx from 'clsx'; import useForm, {ErrorMessages} from '../../../hooks/useForm'; -import useHandleError from '../../../utils/api/handleError'; import usePinturaEditor from '../../../hooks/usePinturaEditor'; -import useRouting from '../../../hooks/useRouting'; import useStaffUsers from '../../../hooks/useStaffUsers'; import validator from 'validator'; import {ConfirmationModal, Heading, Icon, ImageUpload, LimitModal, Menu, MenuItem, Modal, showToast} from '@tryghost/admin-x-design-system'; import {HostLimitError, useLimiter} from '../../../hooks/useLimiter'; -import {RoutingModalProps} from '../../providers/RoutingProvider'; -import {User, canAccessSettings, hasAdminAccess, isAdminUser, isAuthorOrContributor, isEditorUser, isOwnerUser, useDeleteUser, useEditUser, useMakeOwner} from '../../../api/users'; -import {getImageUrl, useUploadImage} from '../../../api/images'; +import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; +import {User, canAccessSettings, hasAdminAccess, isAdminUser, isAuthorOrContributor, isEditorUser, isOwnerUser, useDeleteUser, useEditUser, useMakeOwner} from '@tryghost/admin-x-framework/api/users'; +import {getImageUrl, useUploadImage} from '@tryghost/admin-x-framework/api/images'; import {toast} from 'react-hot-toast'; import {useGlobalData} from '../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; import {validateFacebookUrl, validateTwitterUrl} from '../../../utils/socialUrls'; const validators: Record) => string> = { diff --git a/apps/admin-x-settings/src/components/settings/general/Users.tsx b/apps/admin-x-settings/src/components/settings/general/Users.tsx index 0f0ecaa09f..49eac91bcf 100644 --- a/apps/admin-x-settings/src/components/settings/general/Users.tsx +++ b/apps/admin-x-settings/src/components/settings/general/Users.tsx @@ -1,14 +1,14 @@ import React, {useState} from 'react'; import TopLevelGroup from '../../TopLevelGroup'; import clsx from 'clsx'; -import useHandleError from '../../../utils/api/handleError'; -import useRouting from '../../../hooks/useRouting'; import useStaffUsers from '../../../hooks/useStaffUsers'; import {Avatar, Button, List, ListItem, NoValueLabel, TabView, showToast, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {User, hasAdminAccess, isContributorUser, isEditorUser} from '../../../api/users'; -import {UserInvite, useAddInvite, useDeleteInvite} from '../../../api/invites'; +import {User, hasAdminAccess, isContributorUser, isEditorUser} from '@tryghost/admin-x-framework/api/users'; +import {UserInvite, useAddInvite, useDeleteInvite} from '@tryghost/admin-x-framework/api/invites'; import {generateAvatarColor, getInitials} from '../../../utils/helpers'; import {useGlobalData} from '../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; interface OwnerProps { user: User; diff --git a/apps/admin-x-settings/src/components/settings/general/users/ChangePasswordForm.tsx b/apps/admin-x-settings/src/components/settings/general/users/ChangePasswordForm.tsx index 381796ba0e..9dbb0c6615 100644 --- a/apps/admin-x-settings/src/components/settings/general/users/ChangePasswordForm.tsx +++ b/apps/admin-x-settings/src/components/settings/general/users/ChangePasswordForm.tsx @@ -1,9 +1,9 @@ -import useHandleError from '../../../../utils/api/handleError'; import {Button, Heading, SettingGroup, TextField, showToast} from '@tryghost/admin-x-design-system'; -import {User, useUpdatePassword} from '../../../../api/users'; -import {ValidationError} from '../../../../utils/errors'; +import {User, useUpdatePassword} from '@tryghost/admin-x-framework/api/users'; +import {ValidationError} from '@tryghost/admin-x-framework/errors'; import {useEffect, useRef, useState} from 'react'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const BAD_PASSWORDS = [ '1234567890', diff --git a/apps/admin-x-settings/src/components/settings/general/users/EmailNotifications.tsx b/apps/admin-x-settings/src/components/settings/general/users/EmailNotifications.tsx index 972a51d92d..68326ab9ba 100644 --- a/apps/admin-x-settings/src/components/settings/general/users/EmailNotifications.tsx +++ b/apps/admin-x-settings/src/components/settings/general/users/EmailNotifications.tsx @@ -1,7 +1,7 @@ import CustomHeader from './CustomHeader'; import useFeatureFlag from '../../../../hooks/useFeatureFlag'; import {SettingGroup, SettingGroupContent, Toggle} from '@tryghost/admin-x-design-system'; -import {User, hasAdminAccess} from '../../../../api/users'; +import {User, hasAdminAccess} from '@tryghost/admin-x-framework/api/users'; const EmailNotificationsInputs: React.FC<{ user: User; setUserData: (user: User) => void; }> = ({user, setUserData}) => { const hasWebmentions = useFeatureFlag('webmentions'); diff --git a/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx b/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx index ca6c8d5e0e..1645740c26 100644 --- a/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx +++ b/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx @@ -2,7 +2,7 @@ import CustomHeader from './CustomHeader'; import RoleSelector from './RoleSelector'; import {SettingGroup, SettingGroupContent, TextField} from '@tryghost/admin-x-design-system'; import {UserDetailProps} from '../UserDetailModal'; -import {hasAdminAccess} from '../../../../api/users'; +import {hasAdminAccess} from '@tryghost/admin-x-framework/api/users'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; const BasicInputs: React.FC = ({errors, validateField, clearError, user, setUserData}) => { diff --git a/apps/admin-x-settings/src/components/settings/general/users/RoleSelector.tsx b/apps/admin-x-settings/src/components/settings/general/users/RoleSelector.tsx index e25450b493..b185ebd78c 100644 --- a/apps/admin-x-settings/src/components/settings/general/users/RoleSelector.tsx +++ b/apps/admin-x-settings/src/components/settings/general/users/RoleSelector.tsx @@ -1,6 +1,6 @@ import {Heading, Icon, Radio} from '@tryghost/admin-x-design-system'; -import {User, isOwnerUser} from '../../../../api/users'; -import {useBrowseRoles} from '../../../../api/roles'; +import {User, isOwnerUser} from '@tryghost/admin-x-framework/api/users'; +import {useBrowseRoles} from '@tryghost/admin-x-framework/api/roles'; const RoleSelector: React.FC<{ user: User; setUserData: (user: User) => void; }> = ({user, setUserData}) => { const {data: {roles} = {}} = useBrowseRoles(); diff --git a/apps/admin-x-settings/src/components/settings/general/users/StaffToken.tsx b/apps/admin-x-settings/src/components/settings/general/users/StaffToken.tsx index 51590a2e62..ff88c2f3ab 100644 --- a/apps/admin-x-settings/src/components/settings/general/users/StaffToken.tsx +++ b/apps/admin-x-settings/src/components/settings/general/users/StaffToken.tsx @@ -1,9 +1,9 @@ import APIKeys from '../../advanced/integrations/APIKeys'; import NiceModal from '@ebay/nice-modal-react'; -import useHandleError from '../../../../utils/api/handleError'; import {ConfirmationModal, Heading} from '@tryghost/admin-x-design-system'; -import {genStaffToken, getStaffToken} from '../../../../api/staffToken'; +import {genStaffToken, getStaffToken} from '@tryghost/admin-x-framework/api/staffToken'; import {useEffect, useState} from 'react'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const StaffToken: React.FC = () => { const {refetch: apiKey} = getStaffToken({ diff --git a/apps/admin-x-settings/src/components/settings/membership/Access.tsx b/apps/admin-x-settings/src/components/settings/membership/Access.tsx index 04336cfd06..5994c03109 100644 --- a/apps/admin-x-settings/src/components/settings/membership/Access.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/Access.tsx @@ -4,8 +4,8 @@ import useSettingGroup from '../../../hooks/useSettingGroup'; import {GroupBase, MultiValue} from 'react-select'; import {MultiSelect, MultiSelectOption, Select, SettingGroupContent, withErrorBoundary} from '@tryghost/admin-x-design-system'; import {getOptionLabel} from '../../../utils/helpers'; -import {getSettingValues} from '../../../api/settings'; -import {useBrowseTiers} from '../../../api/tiers'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {useBrowseTiers} from '@tryghost/admin-x-framework/api/tiers'; const MEMBERS_SIGNUP_ACCESS_OPTIONS = [ { diff --git a/apps/admin-x-settings/src/components/settings/membership/Analytics.tsx b/apps/admin-x-settings/src/components/settings/membership/Analytics.tsx index 5908d12f96..9e763a1448 100644 --- a/apps/admin-x-settings/src/components/settings/membership/Analytics.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/Analytics.tsx @@ -2,8 +2,8 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {Button, SettingGroupContent, Toggle, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {getSettingValues} from '../../../api/settings'; -import {usePostsExports} from '../../../api/posts'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {usePostsExports} from '@tryghost/admin-x-framework/api/posts'; const Analytics: React.FC<{ keywords: string[] }> = ({keywords}) => { const { diff --git a/apps/admin-x-settings/src/components/settings/membership/Offers.tsx b/apps/admin-x-settings/src/components/settings/membership/Offers.tsx index 18f9915915..bd3fdb19ce 100644 --- a/apps/admin-x-settings/src/components/settings/membership/Offers.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/Offers.tsx @@ -1,9 +1,9 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useRouting from '../../../hooks/useRouting'; import {Button, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {checkStripeEnabled} from '../../../api/settings'; +import {checkStripeEnabled} from '@tryghost/admin-x-framework/api/settings'; import {useGlobalData} from '../../providers/GlobalDataProvider'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const Offers: React.FC<{ keywords: string[] }> = ({keywords}) => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/membership/Portal.tsx b/apps/admin-x-settings/src/components/settings/membership/Portal.tsx index 5b11813023..5d067bbdbe 100644 --- a/apps/admin-x-settings/src/components/settings/membership/Portal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/Portal.tsx @@ -1,9 +1,9 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useRouting from '../../../hooks/useRouting'; import {Button, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; import {useGlobalData} from '../../providers/GlobalDataProvider'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const Portal: React.FC<{ keywords: string[] }> = ({keywords}) => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/membership/Recommendations.tsx b/apps/admin-x-settings/src/components/settings/membership/Recommendations.tsx index ed47c46745..05260fb183 100644 --- a/apps/admin-x-settings/src/components/settings/membership/Recommendations.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/Recommendations.tsx @@ -2,11 +2,11 @@ import IncomingRecommendationList from './recommendations/IncomingRecommendation import React, {useState} from 'react'; import RecommendationList from './recommendations/RecommendationList'; import TopLevelGroup from '../../TopLevelGroup'; -import useRouting from '../../../hooks/useRouting'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {Button, ShowMoreData, TabView, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {useBrowseIncomingRecommendations, useBrowseRecommendations} from '../../../api/recommendations'; -import {useReferrerHistory} from '../../../api/referrers'; +import {useBrowseIncomingRecommendations, useBrowseRecommendations} from '@tryghost/admin-x-framework/api/recommendations'; +import {useReferrerHistory} from '@tryghost/admin-x-framework/api/referrers'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const Recommendations: React.FC<{ keywords: string[] }> = ({keywords}) => { const { diff --git a/apps/admin-x-settings/src/components/settings/membership/Tiers.tsx b/apps/admin-x-settings/src/components/settings/membership/Tiers.tsx index ec27f1158b..dbedab5b42 100644 --- a/apps/admin-x-settings/src/components/settings/membership/Tiers.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/Tiers.tsx @@ -2,11 +2,11 @@ import React, {useState} from 'react'; import TiersList from './tiers/TiersList'; import TopLevelGroup from '../../TopLevelGroup'; import clsx from 'clsx'; -import useRouting from '../../../hooks/useRouting'; import {Button, StripeButton, TabView, withErrorBoundary} from '@tryghost/admin-x-design-system'; -import {Tier, getActiveTiers, getArchivedTiers, useBrowseTiers} from '../../../api/tiers'; -import {checkStripeEnabled} from '../../../api/settings'; +import {Tier, getActiveTiers, getArchivedTiers, useBrowseTiers} from '@tryghost/admin-x-framework/api/tiers'; +import {checkStripeEnabled} from '@tryghost/admin-x-framework/api/settings'; import {useGlobalData} from '../../providers/GlobalDataProvider'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const StripeConnectedButton: React.FC<{className?: string; onClick: () => void;}> = ({className, onClick}) => { className = clsx( diff --git a/apps/admin-x-settings/src/components/settings/membership/TipsOrDonations.tsx b/apps/admin-x-settings/src/components/settings/membership/TipsOrDonations.tsx index 299b70f0af..64726daddb 100644 --- a/apps/admin-x-settings/src/components/settings/membership/TipsOrDonations.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/TipsOrDonations.tsx @@ -3,7 +3,7 @@ import TopLevelGroup from '../../TopLevelGroup'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {Button, CurrencyField, Heading, Select, SettingGroupContent, confirmIfDirty, withErrorBoundary} from '@tryghost/admin-x-design-system'; import {currencySelectGroups, getSymbol, validateCurrencyAmount} from '../../../utils/currency'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; // Stripe doesn't allow amounts over 10,000 as a preset amount const MAX_AMOUNT = 10_000; diff --git a/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupForm.tsx b/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupForm.tsx index c6d2b03c85..8dffc09960 100644 --- a/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupForm.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupForm.tsx @@ -1,7 +1,7 @@ import React from 'react'; import TopLevelGroup from '../../../TopLevelGroup'; -import useRouting from '../../../../hooks/useRouting'; import {Button} from '@tryghost/admin-x-design-system'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const EmbedSignupForm: React.FC<{ keywords: string[] }> = ({keywords}) => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupFormModal.tsx b/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupFormModal.tsx index b5d0e7d0d1..006b5fb60a 100644 --- a/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupFormModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupFormModal.tsx @@ -1,14 +1,14 @@ import EmbedSignupPreview from './EmbedSignupPreview'; import EmbedSignupSidebar, {SelectedLabelTypes} from './EmbedSignupSidebar'; import NiceModal from '@ebay/nice-modal-react'; -import useRouting from '../../../../hooks/useRouting'; import useSettingGroup from '../../../../hooks/useSettingGroup'; import {Modal, MultiSelectOption} from '@tryghost/admin-x-design-system'; import {MultiValue} from 'react-select'; import {generateCode} from '../../../../utils/generateEmbedCode'; -import {getSettingValues} from '../../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; import {useEffect, useState} from 'react'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const EmbedSignupFormModal = NiceModal.create(() => { let i18nEnabled = false; diff --git a/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupSidebar.tsx b/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupSidebar.tsx index 357e021483..508d284974 100644 --- a/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupSidebar.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/embedSignup/EmbedSignupSidebar.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import useFilterableApi from '../../../../hooks/useFilterableApi'; import {Button, ColorIndicator, ColorPicker, Form, Heading, LoadMultiSelectOptions, MultiSelect, MultiSelectOption, Radio, StickyFooter, TextArea, debounce} from '@tryghost/admin-x-design-system'; -import {Label} from '../../../../api/labels'; +import {Label} from '@tryghost/admin-x-framework/api/labels'; import {MultiValue} from 'react-select'; +import {useFilterableApi} from '@tryghost/admin-x-framework/hooks'; export type SelectedLabelTypes = { label: string; diff --git a/apps/admin-x-settings/src/components/settings/membership/offers/AddOfferModal.tsx b/apps/admin-x-settings/src/components/settings/membership/offers/AddOfferModal.tsx index a0d7e8f77d..57f7228ece 100644 --- a/apps/admin-x-settings/src/components/settings/membership/offers/AddOfferModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/offers/AddOfferModal.tsx @@ -1,13 +1,13 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react'; import PortalFrame from '../portal/PortalFrame'; import useFeatureFlag from '../../../../hooks/useFeatureFlag'; -import useRouting from '../../../../hooks/useRouting'; import {Form, Icon, PreviewModalContent, Select, SelectOption, TextArea, TextField} from '@tryghost/admin-x-design-system'; import {getOfferPortalPreviewUrl, offerPortalPreviewUrlTypes} from '../../../../utils/getOffersPortalPreviewUrl'; -import {getPaidActiveTiers, useBrowseTiers} from '../../../../api/tiers'; +import {getPaidActiveTiers, useBrowseTiers} from '@tryghost/admin-x-framework/api/tiers'; import {getTiersCadences} from '../../../../utils/getTiersCadences'; import {useEffect, useState} from 'react'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; // we should replace this with a library function slugify(text: string): string { @@ -59,10 +59,10 @@ type SidebarProps = { handleTextAreaInput: (e: React.ChangeEvent) => void; }; -const Sidebar: React.FC = ({tierOptions, - handleTierChange, - selectedTier, - handleTextInput, +const Sidebar: React.FC = ({tierOptions, + handleTierChange, + selectedTier, + handleTextInput, typeOptions, durationOptions, handleTypeChange, @@ -141,7 +141,7 @@ const Sidebar: React.FC = ({tierOptions, handleTextInput(e, 'trialAmount'); }} /> } - +
@@ -280,11 +280,11 @@ const AddOfferModal = () => { setOverrides((prevOverrides: offerPortalPreviewUrlTypes) => { // Extract the current value for the key const currentValue = prevOverrides[key]; - + // Check if the current value is an object and has 'isDirty' and 'value' properties if (currentValue && typeof currentValue === 'object' && 'isDirty' in currentValue && 'value' in currentValue) { // Determine if the field has been modified - + return { ...prevOverrides, [key]: { @@ -305,7 +305,7 @@ const AddOfferModal = () => { const handleNameInput = (e: React.ChangeEvent) => { const newValue = e.target.value; - + setOverrides((prevOverrides) => { let newOverrides = {...prevOverrides}; newOverrides.name = newValue; @@ -357,7 +357,7 @@ const AddOfferModal = () => { setHref(newHref); }, [overrides, siteData.url]); - const sidebar = void, errors: ErrorMessages, - offer: Offer, - updateOffer: (fields: Partial) => void, + offer: Offer, + updateOffer: (fields: Partial) => void, validate: () => void}> = ({clearError, errors, offer, updateOffer, validate}) => { - const {siteData} = useGlobalData(); + const {siteData} = useGlobalData(); const [isCopied, setIsCopied] = useState(false); - + const offerUrl = `${getHomepageUrl(siteData!)}${offer?.code}`; const handleCopyClick = async () => { try { diff --git a/apps/admin-x-settings/src/components/settings/membership/offers/OffersModal.tsx b/apps/admin-x-settings/src/components/settings/membership/offers/OffersModal.tsx index 6504893ab8..c2d643126f 100644 --- a/apps/admin-x-settings/src/components/settings/membership/offers/OffersModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/offers/OffersModal.tsx @@ -1,12 +1,12 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react'; import useFeatureFlag from '../../../../hooks/useFeatureFlag'; -import useRouting from '../../../../hooks/useRouting'; import {Button, Modal, Tab, TabView} from '@tryghost/admin-x-design-system'; -import {Tier, getPaidActiveTiers, useBrowseTiers} from '../../../../api/tiers'; +import {Tier, getPaidActiveTiers, useBrowseTiers} from '@tryghost/admin-x-framework/api/tiers'; import {currencyToDecimal, getSymbol} from '../../../../utils/currency'; import {numberWithCommas} from '../../../../utils/helpers'; -import {useBrowseOffers} from '../../../../api/offers'; +import {useBrowseOffers} from '@tryghost/admin-x-framework/api/offers'; import {useEffect, useState} from 'react'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; export type OfferType = 'percent' | 'fixed' | 'trial'; diff --git a/apps/admin-x-settings/src/components/settings/membership/portal/AccountPage.tsx b/apps/admin-x-settings/src/components/settings/membership/portal/AccountPage.tsx index 8a0f5b8ff6..e54eda7011 100644 --- a/apps/admin-x-settings/src/components/settings/membership/portal/AccountPage.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/portal/AccountPage.tsx @@ -1,7 +1,7 @@ import React, {FocusEventHandler, useEffect, useState} from 'react'; import {Form, TextField} from '@tryghost/admin-x-design-system'; -import {SettingValue, getSettingValues} from '../../../../api/settings'; -import {fullEmailAddress, getEmailDomain} from '../../../../api/site'; +import {SettingValue, getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {fullEmailAddress, getEmailDomain} from '@tryghost/admin-x-framework/api/site'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; const AccountPage: React.FC<{ diff --git a/apps/admin-x-settings/src/components/settings/membership/portal/LookAndFeel.tsx b/apps/admin-x-settings/src/components/settings/membership/portal/LookAndFeel.tsx index bc66775ae1..0b9a689547 100644 --- a/apps/admin-x-settings/src/components/settings/membership/portal/LookAndFeel.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/portal/LookAndFeel.tsx @@ -1,14 +1,14 @@ import React, {useState} from 'react'; import clsx from 'clsx'; -import useHandleError from '../../../../utils/api/handleError'; import {Form, Heading, Icon, ImageUpload, Select, TextField, Toggle} from '@tryghost/admin-x-design-system'; import {ReactComponent as PortalIcon1} from '../../../../assets/icons/portal-icon-1.svg'; import {ReactComponent as PortalIcon2} from '../../../../assets/icons/portal-icon-2.svg'; import {ReactComponent as PortalIcon3} from '../../../../assets/icons/portal-icon-3.svg'; import {ReactComponent as PortalIcon4} from '../../../../assets/icons/portal-icon-4.svg'; import {ReactComponent as PortalIcon5} from '../../../../assets/icons/portal-icon-5.svg'; -import {Setting, SettingValue, getSettingValues} from '../../../../api/settings'; -import {getImageUrl, useUploadImage} from '../../../../api/images'; +import {Setting, SettingValue, getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {getImageUrl, useUploadImage} from '@tryghost/admin-x-framework/api/images'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const defaultButtonIcons = [ { diff --git a/apps/admin-x-settings/src/components/settings/membership/portal/PortalLinks.tsx b/apps/admin-x-settings/src/components/settings/membership/portal/PortalLinks.tsx index 138c968c0c..6f52a8008c 100644 --- a/apps/admin-x-settings/src/components/settings/membership/portal/PortalLinks.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/portal/PortalLinks.tsx @@ -1,7 +1,7 @@ import React, {useEffect, useId, useState} from 'react'; import {Button, List, ListItem, ModalPage, Select, TextField} from '@tryghost/admin-x-design-system'; -import {getHomepageUrl} from '../../../../api/site'; -import {getPaidActiveTiers, useBrowseTiers} from '../../../../api/tiers'; +import {getHomepageUrl} from '@tryghost/admin-x-framework/api/site'; +import {getPaidActiveTiers, useBrowseTiers} from '@tryghost/admin-x-framework/api/tiers'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; interface PortalLinkPrefs { diff --git a/apps/admin-x-settings/src/components/settings/membership/portal/PortalModal.tsx b/apps/admin-x-settings/src/components/settings/membership/portal/PortalModal.tsx index b8d845c588..385b5e0543 100644 --- a/apps/admin-x-settings/src/components/settings/membership/portal/PortalModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/portal/PortalModal.tsx @@ -5,15 +5,15 @@ import PortalPreview from './PortalPreview'; import React, {useEffect, useState} from 'react'; import SignupOptions from './SignupOptions'; import useForm, {Dirtyable} from '../../../../hooks/useForm'; -import useHandleError from '../../../../utils/api/handleError'; import useQueryParams from '../../../../hooks/useQueryParams'; -import useRouting from '../../../../hooks/useRouting'; import {ConfirmationModal, PreviewModalContent, Tab, TabView} from '@tryghost/admin-x-design-system'; -import {Setting, SettingValue, getSettingValues, useEditSettings} from '../../../../api/settings'; -import {Tier, useBrowseTiers, useEditTier} from '../../../../api/tiers'; -import {fullEmailAddress} from '../../../../api/site'; +import {Setting, SettingValue, getSettingValues, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; +import {Tier, useBrowseTiers, useEditTier} from '@tryghost/admin-x-framework/api/tiers'; +import {fullEmailAddress} from '@tryghost/admin-x-framework/api/site'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; -import {verifyEmailToken} from '../../../../api/emailVerification'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; +import {verifyEmailToken} from '@tryghost/admin-x-framework/api/emailVerification'; const Sidebar: React.FC<{ localSettings: Setting[] diff --git a/apps/admin-x-settings/src/components/settings/membership/portal/PortalPreview.tsx b/apps/admin-x-settings/src/components/settings/membership/portal/PortalPreview.tsx index f974186702..6666d5baf0 100644 --- a/apps/admin-x-settings/src/components/settings/membership/portal/PortalPreview.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/portal/PortalPreview.tsx @@ -1,8 +1,8 @@ import PortalFrame from './PortalFrame'; import PortalLinks from './PortalLinks'; import React from 'react'; -import {Setting} from '../../../../api/settings'; -import {Tier} from '../../../../api/tiers'; +import {Setting} from '@tryghost/admin-x-framework/api/settings'; +import {Tier} from '@tryghost/admin-x-framework/api/tiers'; import {getPortalPreviewUrl} from '../../../../utils/getPortalPreviewUrl'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; diff --git a/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx b/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx index 72016ea300..157b767f2c 100644 --- a/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useEffect, useMemo} from 'react'; import {CheckboxGroup, CheckboxProps, Form, HtmlField, Toggle} from '@tryghost/admin-x-design-system'; -import {Setting, SettingValue, checkStripeEnabled, getSettingValues} from '../../../../api/settings'; -import {Tier, getPaidActiveTiers} from '../../../../api/tiers'; +import {Setting, SettingValue, checkStripeEnabled, getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {Tier, getPaidActiveTiers} from '@tryghost/admin-x-framework/api/tiers'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; const SignupOptions: React.FC<{ diff --git a/apps/admin-x-settings/src/components/settings/membership/recommendations/AddRecommendationModal.tsx b/apps/admin-x-settings/src/components/settings/membership/recommendations/AddRecommendationModal.tsx index 034efdd31b..a3de781311 100644 --- a/apps/admin-x-settings/src/components/settings/membership/recommendations/AddRecommendationModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/recommendations/AddRecommendationModal.tsx @@ -2,11 +2,10 @@ import AddRecommendationModalConfirm from './AddRecommendationModalConfirm'; import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React, {useEffect, useState} from 'react'; import useForm, {ErrorMessages} from '../../../../hooks/useForm'; -import useRouting from '../../../../hooks/useRouting'; -import {AlreadyExistsError} from '../../../../utils/errors'; -import {EditOrAddRecommendation, useCheckRecommendation} from '../../../../api/recommendations'; +import {AlreadyExistsError} from '@tryghost/admin-x-framework/errors'; +import {EditOrAddRecommendation, useCheckRecommendation} from '@tryghost/admin-x-framework/api/recommendations'; import {Form, LoadingIndicator, Modal, TextField, dismissAllToasts, formatUrl, showToast} from '@tryghost/admin-x-design-system'; -import {RoutingModalProps} from '../../../providers/RoutingProvider'; +import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; import {trimSearchAndHash} from '../../../../utils/url'; interface AddRecommendationModalProps { diff --git a/apps/admin-x-settings/src/components/settings/membership/recommendations/AddRecommendationModalConfirm.tsx b/apps/admin-x-settings/src/components/settings/membership/recommendations/AddRecommendationModalConfirm.tsx index c68498e344..248455902f 100644 --- a/apps/admin-x-settings/src/components/settings/membership/recommendations/AddRecommendationModalConfirm.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/recommendations/AddRecommendationModalConfirm.tsx @@ -4,10 +4,10 @@ import React from 'react'; import RecommendationDescriptionForm, {validateDescriptionForm} from './RecommendationDescriptionForm'; import trackEvent from '../../../../utils/plausible'; import useForm from '../../../../hooks/useForm'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; -import {EditOrAddRecommendation, useAddRecommendation} from '../../../../api/recommendations'; +import {EditOrAddRecommendation, useAddRecommendation} from '@tryghost/admin-x-framework/api/recommendations'; import {Modal, dismissAllToasts, showToast} from '@tryghost/admin-x-design-system'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; interface AddRecommendationModalProps { recommendation: EditOrAddRecommendation, diff --git a/apps/admin-x-settings/src/components/settings/membership/recommendations/EditRecommendationModal.tsx b/apps/admin-x-settings/src/components/settings/membership/recommendations/EditRecommendationModal.tsx index df0f6cbf52..c1b9e67730 100644 --- a/apps/admin-x-settings/src/components/settings/membership/recommendations/EditRecommendationModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/recommendations/EditRecommendationModal.tsx @@ -2,11 +2,10 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React from 'react'; import RecommendationDescriptionForm, {validateDescriptionForm} from './RecommendationDescriptionForm'; import useForm from '../../../../hooks/useForm'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import {ConfirmationModal, Modal, dismissAllToasts, showToast} from '@tryghost/admin-x-design-system'; -import {Recommendation, useDeleteRecommendation, useEditRecommendation} from '../../../../api/recommendations'; -import {RoutingModalProps} from '../../../providers/RoutingProvider'; +import {Recommendation, useDeleteRecommendation, useEditRecommendation} from '@tryghost/admin-x-framework/api/recommendations'; +import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; interface EditRecommendationModalProps { recommendation: Recommendation, diff --git a/apps/admin-x-settings/src/components/settings/membership/recommendations/IncomingRecommendationList.tsx b/apps/admin-x-settings/src/components/settings/membership/recommendations/IncomingRecommendationList.tsx index 24bdbfa230..33c8d7b4ea 100644 --- a/apps/admin-x-settings/src/components/settings/membership/recommendations/IncomingRecommendationList.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/recommendations/IncomingRecommendationList.tsx @@ -1,10 +1,10 @@ import React, {useMemo} from 'react'; import RecommendationIcon from './RecommendationIcon'; -import useRouting from '../../../../hooks/useRouting'; import {Button, NoValueLabel, PaginationData, ShowMoreData, Table, TableCell, TableRow} from '@tryghost/admin-x-design-system'; -import {IncomingRecommendation} from '../../../../api/recommendations'; -import {ReferrerHistoryItem} from '../../../../api/referrers'; +import {IncomingRecommendation} from '@tryghost/admin-x-framework/api/recommendations'; +import {ReferrerHistoryItem} from '@tryghost/admin-x-framework/api/referrers'; import {numberWithCommas} from '../../../../utils/helpers'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; interface IncomingRecommendationListProps { incomingRecommendations: IncomingRecommendation[], diff --git a/apps/admin-x-settings/src/components/settings/membership/recommendations/RecommendationDescriptionForm.tsx b/apps/admin-x-settings/src/components/settings/membership/recommendations/RecommendationDescriptionForm.tsx index 880b31a33c..a168323066 100644 --- a/apps/admin-x-settings/src/components/settings/membership/recommendations/RecommendationDescriptionForm.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/recommendations/RecommendationDescriptionForm.tsx @@ -1,6 +1,6 @@ import React from 'react'; import RecommendationIcon from './RecommendationIcon'; -import {EditOrAddRecommendation, Recommendation} from '../../../../api/recommendations'; +import {EditOrAddRecommendation, Recommendation} from '@tryghost/admin-x-framework/api/recommendations'; import {ErrorMessages} from '../../../../hooks/useForm'; import {Form, Heading, Hint, TextArea, TextField, URLTextField} from '@tryghost/admin-x-design-system'; diff --git a/apps/admin-x-settings/src/components/settings/membership/recommendations/RecommendationList.tsx b/apps/admin-x-settings/src/components/settings/membership/recommendations/RecommendationList.tsx index e9e78364aa..872f490ed2 100644 --- a/apps/admin-x-settings/src/components/settings/membership/recommendations/RecommendationList.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/recommendations/RecommendationList.tsx @@ -2,11 +2,11 @@ import EditRecommendationModal from './EditRecommendationModal'; import NiceModal from '@ebay/nice-modal-react'; import React, {useState} from 'react'; import RecommendationIcon from './RecommendationIcon'; -import useRouting from '../../../../hooks/useRouting'; import useSettingGroup from '../../../../hooks/useSettingGroup'; import {Button, Link, NoValueLabel, PaginationData, ShowMoreData, Table, TableCell, TableRow, Tooltip} from '@tryghost/admin-x-design-system'; -import {Recommendation} from '../../../../api/recommendations'; +import {Recommendation} from '@tryghost/admin-x-framework/api/recommendations'; import {numberWithCommas} from '../../../../utils/helpers'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; interface RecommendationListProps { recommendations: Recommendation[], diff --git a/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx b/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx index 3d86ddf184..dde833afc9 100644 --- a/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx @@ -4,18 +4,18 @@ import GhostLogoPink from '../../../../assets/images/orb-pink.png'; import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React, {useState} from 'react'; import StripeLogo from '../../../../assets/images/stripe-emblem.svg'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import useSettingGroup from '../../../../hooks/useSettingGroup'; import {Button, ConfirmationModal, Form, Heading, Modal, StripeButton, TextArea, TextField, Toggle, showToast} from '@tryghost/admin-x-design-system'; -import {JSONError} from '../../../../utils/errors'; +import {JSONError} from '@tryghost/admin-x-framework/errors'; import {ReactComponent as StripeVerified} from '../../../../assets/images/stripe-verified.svg'; -import {checkStripeEnabled, getSettingValue, getSettingValues, useDeleteStripeSettings, useEditSettings} from '../../../../api/settings'; -import {getGhostPaths} from '../../../../utils/helpers'; +import {checkStripeEnabled, getSettingValue, getSettingValues, useDeleteStripeSettings, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; +import {getGhostPaths} from '@tryghost/admin-x-framework/helpers'; import {toast} from 'react-hot-toast'; -import {useBrowseMembers} from '../../../../api/members'; -import {useBrowseTiers, useEditTier} from '../../../../api/tiers'; +import {useBrowseMembers} from '@tryghost/admin-x-framework/api/members'; +import {useBrowseTiers, useEditTier} from '@tryghost/admin-x-framework/api/tiers'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const RETRY_PRODUCT_SAVE_POLL_LENGTH = 1000; const RETRY_PRODUCT_SAVE_MAX_POLL = 15 * RETRY_PRODUCT_SAVE_POLL_LENGTH; diff --git a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx index 5b8edac3a3..8f58c138b6 100644 --- a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx @@ -2,15 +2,14 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React, {useEffect, useRef} from 'react'; import TierDetailPreview from './TierDetailPreview'; import useForm, {ErrorMessages} from '../../../../hooks/useForm'; -import useHandleError from '../../../../utils/api/handleError'; -import useRouting from '../../../../hooks/useRouting'; import useSettingGroup from '../../../../hooks/useSettingGroup'; import {Button, ButtonProps, ConfirmationModal, CurrencyField, Form, Heading, Icon, Modal, Select, SortableList, TextField, Toggle, URLTextField, showToast, useSortableIndexedList} from '@tryghost/admin-x-design-system'; -import {RoutingModalProps} from '../../../providers/RoutingProvider'; -import {Tier, useAddTier, useBrowseTiers, useEditTier} from '../../../../api/tiers'; +import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; +import {Tier, useAddTier, useBrowseTiers, useEditTier} from '@tryghost/admin-x-framework/api/tiers'; import {currencies, currencySelectGroups, validateCurrencyAmount} from '../../../../utils/currency'; -import {getSettingValues} from '../../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; import {toast} from 'react-hot-toast'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; export type TierFormState = Partial> & { trial_days: string; diff --git a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailPreview.tsx b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailPreview.tsx index 9b6624d07e..dd4a1b895a 100644 --- a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailPreview.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailPreview.tsx @@ -4,7 +4,7 @@ import useSettingGroup from '../../../../hooks/useSettingGroup'; import {Button, Heading, Icon} from '@tryghost/admin-x-design-system'; import {TierFormState} from './TierDetailModal'; import {currencyToDecimal, getSymbol} from '../../../../utils/currency'; -import {getSettingValues} from '../../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; import {numberWithCommas} from '../../../../utils/helpers'; interface TierDetailPreviewProps { diff --git a/apps/admin-x-settings/src/components/settings/membership/tiers/TiersList.tsx b/apps/admin-x-settings/src/components/settings/membership/tiers/TiersList.tsx index 28340d0dbc..28c5c6dca1 100644 --- a/apps/admin-x-settings/src/components/settings/membership/tiers/TiersList.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/tiers/TiersList.tsx @@ -1,11 +1,11 @@ import React from 'react'; import clsx from 'clsx'; -import useRouting from '../../../../hooks/useRouting'; import {Icon, NoValueLabel} from '@tryghost/admin-x-design-system'; -import {Tier} from '../../../../api/tiers'; +import {Tier} from '@tryghost/admin-x-framework/api/tiers'; import {TrialDaysLabel} from './TierDetailPreview'; import {currencyToDecimal, getSymbol} from '../../../../utils/currency'; import {numberWithCommas} from '../../../../utils/helpers'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; interface TiersListProps { tab?: 'active-tiers' | 'archive-tiers' | 'free-tier'; diff --git a/apps/admin-x-settings/src/components/settings/site/AnnouncementBar.tsx b/apps/admin-x-settings/src/components/settings/site/AnnouncementBar.tsx index 9aedd1e967..2bfaae75f9 100644 --- a/apps/admin-x-settings/src/components/settings/site/AnnouncementBar.tsx +++ b/apps/admin-x-settings/src/components/settings/site/AnnouncementBar.tsx @@ -1,7 +1,7 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useRouting from '../../../hooks/useRouting'; import {Button, withErrorBoundary} from '@tryghost/admin-x-design-system'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const AnnouncementBar: React.FC<{ keywords: string[] }> = ({keywords}) => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx b/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx index 6f86238490..90392dcfb6 100644 --- a/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx +++ b/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx @@ -1,13 +1,13 @@ import AnnouncementBarPreview from './announcementBar/AnnouncementBarPreview'; import NiceModal from '@ebay/nice-modal-react'; import React, {useCallback, useState} from 'react'; -import useRouting from '../../../hooks/useRouting'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {CheckboxGroup, ColorIndicator, Form, HtmlField, PreviewModalContent, Tab, showToast} from '@tryghost/admin-x-design-system'; -import {getHomepageUrl} from '../../../api/site'; -import {getSettingValues} from '../../../api/settings'; -import {useBrowsePosts} from '../../../api/posts'; +import {getHomepageUrl} from '@tryghost/admin-x-framework/api/site'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {useBrowsePosts} from '@tryghost/admin-x-framework/api/posts'; import {useGlobalData} from '../../providers/GlobalDataProvider'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; type SidebarProps = { announcementContent?: string; diff --git a/apps/admin-x-settings/src/components/settings/site/DesignAndThemeModal.tsx b/apps/admin-x-settings/src/components/settings/site/DesignAndThemeModal.tsx index 575bef2999..26be07aced 100644 --- a/apps/admin-x-settings/src/components/settings/site/DesignAndThemeModal.tsx +++ b/apps/admin-x-settings/src/components/settings/site/DesignAndThemeModal.tsx @@ -1,7 +1,7 @@ import ChangeThemeModal from './ThemeModal'; import DesignModal from './DesignModal'; import NiceModal, {useModal} from '@ebay/nice-modal-react'; -import {RoutingModalProps} from '../../providers/RoutingProvider'; +import {RoutingModalProps} from '@tryghost/admin-x-framework/routing'; const DesignAndThemeModal: React.FC = ({pathName}) => { const modal = useModal(); diff --git a/apps/admin-x-settings/src/components/settings/site/DesignModal.tsx b/apps/admin-x-settings/src/components/settings/site/DesignModal.tsx index 38ea744abe..0a987f4979 100644 --- a/apps/admin-x-settings/src/components/settings/site/DesignModal.tsx +++ b/apps/admin-x-settings/src/components/settings/site/DesignModal.tsx @@ -3,15 +3,15 @@ import React, {useEffect, useState} from 'react'; import ThemePreview from './designAndBranding/ThemePreview'; import ThemeSettings from './designAndBranding/ThemeSettings'; import useForm from '../../../hooks/useForm'; -import useHandleError from '../../../utils/api/handleError'; -import useRouting from '../../../hooks/useRouting'; -import {CustomThemeSetting, useBrowseCustomThemeSettings, useEditCustomThemeSettings} from '../../../api/customThemeSettings'; +import {CustomThemeSetting, useBrowseCustomThemeSettings, useEditCustomThemeSettings} from '@tryghost/admin-x-framework/api/customThemeSettings'; import {Icon, PreviewModalContent, StickyFooter, Tab, TabView} from '@tryghost/admin-x-design-system'; -import {Setting, SettingValue, getSettingValues, useEditSettings} from '../../../api/settings'; -import {getHomepageUrl} from '../../../api/site'; -import {useBrowsePosts} from '../../../api/posts'; -import {useBrowseThemes} from '../../../api/themes'; +import {Setting, SettingValue, getSettingValues, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; +import {getHomepageUrl} from '@tryghost/admin-x-framework/api/site'; +import {useBrowsePosts} from '@tryghost/admin-x-framework/api/posts'; +import {useBrowseThemes} from '@tryghost/admin-x-framework/api/themes'; import {useGlobalData} from '../../providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const Sidebar: React.FC<{ brandSettings: BrandSettingValues diff --git a/apps/admin-x-settings/src/components/settings/site/DesignSetting.tsx b/apps/admin-x-settings/src/components/settings/site/DesignSetting.tsx index 7a2db287b8..f406c9c595 100644 --- a/apps/admin-x-settings/src/components/settings/site/DesignSetting.tsx +++ b/apps/admin-x-settings/src/components/settings/site/DesignSetting.tsx @@ -1,7 +1,7 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useRouting from '../../../hooks/useRouting'; import {Button, withErrorBoundary} from '@tryghost/admin-x-design-system'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const DesignSetting: React.FC<{ keywords: string[] }> = ({keywords}) => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/site/Navigation.tsx b/apps/admin-x-settings/src/components/settings/site/Navigation.tsx index da2b117c61..502ddeb923 100644 --- a/apps/admin-x-settings/src/components/settings/site/Navigation.tsx +++ b/apps/admin-x-settings/src/components/settings/site/Navigation.tsx @@ -1,7 +1,7 @@ import React from 'react'; import TopLevelGroup from '../../TopLevelGroup'; -import useRouting from '../../../hooks/useRouting'; import {Button, withErrorBoundary} from '@tryghost/admin-x-design-system'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; const Navigation: React.FC<{ keywords: string[] }> = ({keywords}) => { const {updateRoute} = useRouting(); diff --git a/apps/admin-x-settings/src/components/settings/site/NavigationModal.tsx b/apps/admin-x-settings/src/components/settings/site/NavigationModal.tsx index cc776a4a51..2944be9f39 100644 --- a/apps/admin-x-settings/src/components/settings/site/NavigationModal.tsx +++ b/apps/admin-x-settings/src/components/settings/site/NavigationModal.tsx @@ -1,10 +1,10 @@ import NavigationEditForm from './navigation/NavigationEditForm'; import NiceModal, {useModal} from '@ebay/nice-modal-react'; import useNavigationEditor, {NavigationItem} from '../../../hooks/site/useNavigationEditor'; -import useRouting from '../../../hooks/useRouting'; import useSettingGroup from '../../../hooks/useSettingGroup'; import {Modal, TabView} from '@tryghost/admin-x-design-system'; -import {getSettingValues} from '../../../api/settings'; +import {getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; import {useState} from 'react'; const NavigationModal = NiceModal.create(() => { diff --git a/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx b/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx index af0961cc4a..dbf034933f 100644 --- a/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx +++ b/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx @@ -5,12 +5,12 @@ import OfficialThemes from './theme/OfficialThemes'; import React, {useEffect, useRef, useState} from 'react'; import ThemeInstalledModal from './theme/ThemeInstalledModal'; import ThemePreview from './theme/ThemePreview'; -import useHandleError from '../../../utils/api/handleError'; -import useRouting from '../../../hooks/useRouting'; import {Breadcrumbs, Button, ConfirmationModal, FileUpload, LimitModal, Modal, PageHeader, TabView, showToast} from '@tryghost/admin-x-design-system'; import {HostLimitError, useLimiter} from '../../../hooks/useLimiter'; -import {InstalledTheme, Theme, ThemesInstallResponseType, isDefaultOrLegacyTheme, useActivateTheme, useBrowseThemes, useInstallTheme, useUploadTheme} from '../../../api/themes'; -import {OfficialTheme} from '../../providers/ServiceProvider'; +import {InstalledTheme, Theme, ThemesInstallResponseType, isDefaultOrLegacyTheme, useActivateTheme, useBrowseThemes, useInstallTheme, useUploadTheme} from '@tryghost/admin-x-framework/api/themes'; +import {OfficialTheme} from '../../providers/SettingsAppProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; +import {useRouting} from '@tryghost/admin-x-framework/routing'; interface ThemeToolbarProps { selectedTheme: OfficialTheme|null; diff --git a/apps/admin-x-settings/src/components/settings/site/designAndBranding/BrandSettings.tsx b/apps/admin-x-settings/src/components/settings/site/designAndBranding/BrandSettings.tsx index 0f2f3c05af..3d83937dab 100644 --- a/apps/admin-x-settings/src/components/settings/site/designAndBranding/BrandSettings.tsx +++ b/apps/admin-x-settings/src/components/settings/site/designAndBranding/BrandSettings.tsx @@ -1,12 +1,12 @@ import React, {useRef, useState} from 'react'; import UnsplashSearchModal from '../../../../unsplash/UnsplashSearchModal'; -import useHandleError from '../../../../utils/api/handleError'; import usePinturaEditor from '../../../../hooks/usePinturaEditor'; import {ColorPickerField, Heading, Hint, ImageUpload, SettingGroupContent, TextField, debounce} from '@tryghost/admin-x-design-system'; -import {SettingValue, getSettingValues} from '../../../../api/settings'; -import {getImageUrl, useUploadImage} from '../../../../api/images'; +import {SettingValue, getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {getImageUrl, useUploadImage} from '@tryghost/admin-x-framework/api/images'; +import {useFramework} from '@tryghost/admin-x-framework'; import {useGlobalData} from '../../../providers/GlobalDataProvider'; -import {useServices} from '../../../providers/ServiceProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; export interface BrandSettingValues { description: string @@ -22,7 +22,7 @@ const BrandSettings: React.FC<{ values: BrandSettingValues, updateSetting: (key: const {settings} = useGlobalData(); const [unsplashEnabled] = getSettingValues(settings, ['unsplash']); const [showUnsplash, setShowUnsplash] = useState(false); - const {unsplashConfig} = useServices(); + const {unsplashConfig} = useFramework(); const handleError = useHandleError(); const updateDescriptionDebouncedRef = useRef( diff --git a/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemePreview.tsx b/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemePreview.tsx index d747d33760..4fe2b49b0d 100644 --- a/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemePreview.tsx +++ b/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemePreview.tsx @@ -1,6 +1,6 @@ import IframeBuffering from '../../../../utils/IframeBuffering'; import React, {useCallback} from 'react'; -import {CustomThemeSetting, hiddenCustomThemeSettingValue} from '../../../../api/customThemeSettings'; +import {CustomThemeSetting, hiddenCustomThemeSettingValue} from '@tryghost/admin-x-framework/api/customThemeSettings'; import {isCustomThemeSettingVisible} from '../../../../utils/isCustomThemeSettingsVisible'; type BrandSettings = { diff --git a/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemeSettings.tsx b/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemeSettings.tsx index 1fd2cca8b2..c913bc3e01 100644 --- a/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemeSettings.tsx +++ b/apps/admin-x-settings/src/components/settings/site/designAndBranding/ThemeSettings.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import useHandleError from '../../../../utils/api/handleError'; import {ColorPickerField, Heading, Hint, ImageUpload, Select, SettingGroupContent, TextField, Toggle} from '@tryghost/admin-x-design-system'; -import {CustomThemeSetting} from '../../../../api/customThemeSettings'; -import {getImageUrl, useUploadImage} from '../../../../api/images'; -import {humanizeSettingKey} from '../../../../api/settings'; +import {CustomThemeSetting} from '@tryghost/admin-x-framework/api/customThemeSettings'; +import {getImageUrl, useUploadImage} from '@tryghost/admin-x-framework/api/images'; +import {humanizeSettingKey} from '@tryghost/admin-x-framework/api/settings'; import {isCustomThemeSettingVisible} from '../../../../utils/isCustomThemeSettingsVisible'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; const ThemeSetting: React.FC<{ setting: CustomThemeSetting, diff --git a/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx b/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx index b89a1f8ad4..6751346f5e 100644 --- a/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx +++ b/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx @@ -1,9 +1,9 @@ import NiceModal from '@ebay/nice-modal-react'; import React from 'react'; -import useHandleError from '../../../../utils/api/handleError'; import {Button, ButtonProps, ConfirmationModal, List, ListItem, Menu, ModalPage} from '@tryghost/admin-x-design-system'; -import {Theme, isActiveTheme, isDefaultTheme, isDeletableTheme, isLegacyTheme, useActivateTheme, useDeleteTheme} from '../../../../api/themes'; -import {downloadFile, getGhostPaths} from '../../../../utils/helpers'; +import {Theme, isActiveTheme, isDefaultTheme, isDeletableTheme, isLegacyTheme, useActivateTheme, useDeleteTheme} from '@tryghost/admin-x-framework/api/themes'; +import {downloadFile, getGhostPaths} from '@tryghost/admin-x-framework/helpers'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; interface ThemeActionProps { theme: Theme; diff --git a/apps/admin-x-settings/src/components/settings/site/theme/InvalidThemeModal.tsx b/apps/admin-x-settings/src/components/settings/site/theme/InvalidThemeModal.tsx index 0fc4a35cf2..1a52a35c95 100644 --- a/apps/admin-x-settings/src/components/settings/site/theme/InvalidThemeModal.tsx +++ b/apps/admin-x-settings/src/components/settings/site/theme/InvalidThemeModal.tsx @@ -1,7 +1,7 @@ import NiceModal from '@ebay/nice-modal-react'; import React, {ReactNode, useState} from 'react'; import {Button, ConfirmationModalContent, Heading, List, ListItem} from '@tryghost/admin-x-design-system'; -import {ThemeProblem} from '../../../../api/themes'; +import {ThemeProblem} from '@tryghost/admin-x-framework/api/themes'; type FatalError = { details: { diff --git a/apps/admin-x-settings/src/components/settings/site/theme/OfficialThemes.tsx b/apps/admin-x-settings/src/components/settings/site/theme/OfficialThemes.tsx index fe323f6590..4d179d29c2 100644 --- a/apps/admin-x-settings/src/components/settings/site/theme/OfficialThemes.tsx +++ b/apps/admin-x-settings/src/components/settings/site/theme/OfficialThemes.tsx @@ -2,8 +2,9 @@ import MarketplaceBgImage from '../../../../assets/images/footer-marketplace-bg. import React, {useEffect, useState} from 'react'; import clsx from 'clsx'; import {Heading, ModalPage} from '@tryghost/admin-x-design-system'; -import {OfficialTheme, ThemeVariant, useOfficialThemes} from '../../../providers/ServiceProvider'; -import {getGhostPaths, resolveAsset} from '../../../../utils/helpers'; +import {OfficialTheme, ThemeVariant, useOfficialThemes} from '../../../providers/SettingsAppProvider'; +import {getGhostPaths} from '@tryghost/admin-x-framework/helpers'; +import {resolveAsset} from '../../../../utils/helpers'; const VARIANT_LOOP_INTERVAL = 3000; diff --git a/apps/admin-x-settings/src/components/settings/site/theme/ThemeInstalledModal.tsx b/apps/admin-x-settings/src/components/settings/site/theme/ThemeInstalledModal.tsx index da2a879f5e..1c3c4aa97a 100644 --- a/apps/admin-x-settings/src/components/settings/site/theme/ThemeInstalledModal.tsx +++ b/apps/admin-x-settings/src/components/settings/site/theme/ThemeInstalledModal.tsx @@ -1,8 +1,8 @@ import NiceModal from '@ebay/nice-modal-react'; import React, {ReactNode, useState} from 'react'; -import useHandleError from '../../../../utils/api/handleError'; import {Button, ConfirmationModalContent, Heading, List, ListItem, showToast} from '@tryghost/admin-x-design-system'; -import {InstalledTheme, ThemeProblem, useActivateTheme} from '../../../../api/themes'; +import {InstalledTheme, ThemeProblem, useActivateTheme} from '@tryghost/admin-x-framework/api/themes'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; export const ThemeProblemView = ({problem}:{problem: ThemeProblem}) => { const [isExpanded, setExpanded] = useState(false); diff --git a/apps/admin-x-settings/src/components/settings/site/theme/ThemePreview.tsx b/apps/admin-x-settings/src/components/settings/site/theme/ThemePreview.tsx index cf994e7ced..e730263a73 100644 --- a/apps/admin-x-settings/src/components/settings/site/theme/ThemePreview.tsx +++ b/apps/admin-x-settings/src/components/settings/site/theme/ThemePreview.tsx @@ -1,8 +1,8 @@ import NiceModal from '@ebay/nice-modal-react'; import React, {useState} from 'react'; import {Breadcrumbs, Button, ButtonGroup, ConfirmationModal, DesktopChrome, MobileChrome, PageHeader, Select, SelectOption} from '@tryghost/admin-x-design-system'; -import {OfficialTheme, ThemeVariant} from '../../../providers/ServiceProvider'; -import {Theme, isDefaultOrLegacyTheme} from '../../../../api/themes'; +import {OfficialTheme, ThemeVariant} from '../../../providers/SettingsAppProvider'; +import {Theme, isDefaultOrLegacyTheme} from '@tryghost/admin-x-framework/api/themes'; const hasVariants = (theme: OfficialTheme) => theme.variants && theme.variants.length > 0; diff --git a/apps/admin-x-settings/src/hooks/useLimiter.tsx b/apps/admin-x-settings/src/hooks/useLimiter.tsx index 847a07b290..6631f72751 100644 --- a/apps/admin-x-settings/src/hooks/useLimiter.tsx +++ b/apps/admin-x-settings/src/hooks/useLimiter.tsx @@ -1,6 +1,6 @@ import useStaffUsers from './useStaffUsers'; -import {useBrowseMembers} from '../api/members'; -import {useBrowseNewsletters} from '../api/newsletters'; +import {useBrowseMembers} from '@tryghost/admin-x-framework/api/members'; +import {useBrowseNewsletters} from '@tryghost/admin-x-framework/api/newsletters'; import {useEffect, useMemo, useState} from 'react'; import {useGlobalData} from '../components/providers/GlobalDataProvider'; diff --git a/apps/admin-x-settings/src/hooks/usePinturaEditor.ts b/apps/admin-x-settings/src/hooks/usePinturaEditor.ts index 75d7277175..3228290bc4 100644 --- a/apps/admin-x-settings/src/hooks/usePinturaEditor.ts +++ b/apps/admin-x-settings/src/hooks/usePinturaEditor.ts @@ -1,8 +1,7 @@ import * as Sentry from '@sentry/react'; -import {Config} from '../api/config'; -import {Setting} from '../api/settings'; -import {getGhostPaths} from '../utils/helpers'; -import {getSettingValues} from '../api/settings'; +import {Config} from '@tryghost/admin-x-framework/api/config'; +import {Setting, getSettingValues} from '@tryghost/admin-x-framework/api/settings'; +import {getGhostPaths} from '@tryghost/admin-x-framework/helpers'; import {useCallback, useEffect, useRef, useState} from 'react'; import {useGlobalData} from '../components/providers/GlobalDataProvider'; diff --git a/apps/admin-x-settings/src/hooks/useRouting.tsx b/apps/admin-x-settings/src/hooks/useRouting.tsx deleted file mode 100644 index 576406de26..0000000000 --- a/apps/admin-x-settings/src/hooks/useRouting.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import {RouteContext} from '../components/providers/RoutingProvider'; -import {useContext} from 'react'; - -const useRouting = () => useContext(RouteContext); - -export default useRouting; diff --git a/apps/admin-x-settings/src/hooks/useSettingGroup.tsx b/apps/admin-x-settings/src/hooks/useSettingGroup.tsx index cfcc5d8035..7ba7436d23 100644 --- a/apps/admin-x-settings/src/hooks/useSettingGroup.tsx +++ b/apps/admin-x-settings/src/hooks/useSettingGroup.tsx @@ -1,11 +1,11 @@ import React, {useEffect, useRef, useState} from 'react'; import useForm, {ErrorMessages, OkProps, SaveHandler, SaveState} from './useForm'; -import useHandleError from '../utils/api/handleError'; -import {Setting, SettingValue, useEditSettings} from '../api/settings'; -import {SiteData} from '../api/site'; +import {Setting, SettingValue, useEditSettings} from '@tryghost/admin-x-framework/api/settings'; +import {SiteData} from '@tryghost/admin-x-framework/api/site'; import {showToast, useGlobalDirtyState} from '@tryghost/admin-x-design-system'; import {toast} from 'react-hot-toast'; import {useGlobalData} from '../components/providers/GlobalDataProvider'; +import {useHandleError} from '@tryghost/admin-x-framework/hooks'; interface LocalSetting extends Setting { dirty?: boolean; diff --git a/apps/admin-x-settings/src/hooks/useStaffUsers.tsx b/apps/admin-x-settings/src/hooks/useStaffUsers.tsx index 97dfb74b40..60e3d6f312 100644 --- a/apps/admin-x-settings/src/hooks/useStaffUsers.tsx +++ b/apps/admin-x-settings/src/hooks/useStaffUsers.tsx @@ -1,6 +1,6 @@ -import {User, useBrowseUsers} from '../api/users'; -import {UserInvite, useBrowseInvites} from '../api/invites'; -import {useBrowseRoles} from '../api/roles'; +import {User, useBrowseUsers} from '@tryghost/admin-x-framework/api/users'; +import {UserInvite, useBrowseInvites} from '@tryghost/admin-x-framework/api/invites'; +import {useBrowseRoles} from '@tryghost/admin-x-framework/api/roles'; import {useGlobalData} from '../components/providers/GlobalDataProvider'; import {useMemo} from 'react'; diff --git a/apps/admin-x-settings/src/main.tsx b/apps/admin-x-settings/src/main.tsx index c2638c54ce..a15058704e 100644 --- a/apps/admin-x-settings/src/main.tsx +++ b/apps/admin-x-settings/src/main.tsx @@ -8,6 +8,7 @@ import {DefaultHeaderTypes} from './unsplash/UnsplashTypes.ts'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( {}} fetchKoenigLexical={() => { return Promise.resolve(); diff --git a/apps/admin-x-settings/src/utils/getPortalPreviewUrl.ts b/apps/admin-x-settings/src/utils/getPortalPreviewUrl.ts index 672690420a..7be4576717 100644 --- a/apps/admin-x-settings/src/utils/getPortalPreviewUrl.ts +++ b/apps/admin-x-settings/src/utils/getPortalPreviewUrl.ts @@ -1,7 +1,7 @@ -import {Config} from '../api/config'; -import {Setting, checkStripeEnabled, getSettingValue} from '../api/settings'; -import {SiteData} from '../api/site'; -import {Tier} from '../api/tiers'; +import {Config} from '@tryghost/admin-x-framework/api/config'; +import {Setting, checkStripeEnabled, getSettingValue} from '@tryghost/admin-x-framework/api/settings'; +import {SiteData} from '@tryghost/admin-x-framework/api/site'; +import {Tier} from '@tryghost/admin-x-framework/api/tiers'; export type portalPreviewUrlTypes = { settings: Setting[]; diff --git a/apps/admin-x-settings/src/utils/getTiersCadences.ts b/apps/admin-x-settings/src/utils/getTiersCadences.ts index 7840103bf1..9bed713112 100644 --- a/apps/admin-x-settings/src/utils/getTiersCadences.ts +++ b/apps/admin-x-settings/src/utils/getTiersCadences.ts @@ -1,9 +1,9 @@ import {SelectOption} from '@tryghost/admin-x-design-system'; -import {Tier} from '../api/tiers'; +import {Tier} from '@tryghost/admin-x-framework/api/tiers'; export const getTiersCadences = (tiers: Tier[]): SelectOption[] => { const cadences: SelectOption[] = []; - + tiers.forEach((tier: Tier) => { cadences.push({ label: `${tier.name} - Monthly`, diff --git a/apps/admin-x-settings/src/utils/globalTypes.ts b/apps/admin-x-settings/src/utils/globalTypes.ts deleted file mode 100644 index b6e0beffef..0000000000 --- a/apps/admin-x-settings/src/utils/globalTypes.ts +++ /dev/null @@ -1,6 +0,0 @@ -// a rather miscellaneous collection of types - will remove in future - -export type UpgradeStatusType = { - isRequired: boolean; - message: string; -} diff --git a/apps/admin-x-settings/src/utils/helpers.ts b/apps/admin-x-settings/src/utils/helpers.ts index 2622e5c031..e850bcdc6e 100644 --- a/apps/admin-x-settings/src/utils/helpers.ts +++ b/apps/admin-x-settings/src/utils/helpers.ts @@ -1,19 +1,3 @@ -export interface IGhostPaths { - subdir: string; - adminRoot: string; - assetRoot: string; - apiRoot: string; -} - -export function getGhostPaths(): IGhostPaths { - let path = window.location.pathname; - let subdir = path.substr(0, path.search('/ghost/')); - let adminRoot = `${subdir}/ghost/`; - let assetRoot = `${subdir}/ghost/assets/`; - let apiRoot = `${subdir}/ghost/api/admin`; - return {subdir, adminRoot, assetRoot, apiRoot}; -} - export function resolveAsset(assetPath: string, relativeTo: string) { if (assetPath.match(/^(?:[a-z]+:)?\/\//i)) { return assetPath; @@ -59,23 +43,6 @@ export function generateAvatarColor(name: string) { return 'hsl(' + h + ', ' + s + '%, ' + l + '%)'; } -export function downloadFile(url: string) { - let iframe = document.getElementById('iframeDownload'); - - if (!iframe) { - iframe = document.createElement('iframe'); - iframe.id = 'iframeDownload'; - iframe.style.display = 'none'; - document.body.append(iframe); - } - - iframe.setAttribute('src', url); -} - -export function downloadFromEndpoint(path: string) { - downloadFile(`${getGhostPaths().apiRoot}${path}`); -} - export function numberWithCommas(x: number) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } diff --git a/apps/admin-x-settings/src/utils/isCustomThemeSettingsVisible.ts b/apps/admin-x-settings/src/utils/isCustomThemeSettingsVisible.ts index 717f7cf170..0107fb2375 100644 --- a/apps/admin-x-settings/src/utils/isCustomThemeSettingsVisible.ts +++ b/apps/admin-x-settings/src/utils/isCustomThemeSettingsVisible.ts @@ -1,5 +1,5 @@ import nql from '@tryghost/nql'; -import {CustomThemeSetting} from '../api/customThemeSettings'; +import {CustomThemeSetting} from '@tryghost/admin-x-framework/api/customThemeSettings'; export function isCustomThemeSettingVisible(setting: CustomThemeSetting, settingsKeyValueObj: Record) { if (!setting.visibility) {