diff --git a/ghost/admin-x-settings/src/App.tsx b/ghost/admin-x-settings/src/App.tsx index f903692de1..0daaaff96b 100644 --- a/ghost/admin-x-settings/src/App.tsx +++ b/ghost/admin-x-settings/src/App.tsx @@ -1,9 +1,9 @@ import Button from './admin-x-ds/global/Button'; +import DataProvider from './components/providers/DataProvider'; import Heading from './admin-x-ds/global/Heading'; import NiceModal from '@ebay/nice-modal-react'; import Settings from './components/Settings'; import Sidebar from './components/Sidebar'; -import {SettingsProvider} from './components/SettingsProvider'; function App() { return ( @@ -26,9 +26,9 @@ function App() {
- + - +
diff --git a/ghost/admin-x-settings/src/components/Settings.tsx b/ghost/admin-x-settings/src/components/Settings.tsx index e9513b1f3e..7b170841fc 100644 --- a/ghost/admin-x-settings/src/components/Settings.tsx +++ b/ghost/admin-x-settings/src/components/Settings.tsx @@ -3,7 +3,7 @@ import React, {useContext} from 'react'; import EmailSettings from './settings/email/EmailSettings'; import GeneralSettings from './settings/general/GeneralSettings'; import MembershipSettings from './settings/membership/MembershipSettings'; -import {SettingsContext} from './SettingsProvider'; +import {SettingsContext} from './providers/SettingsProvider'; const Settings: React.FC = () => { const {settings} = useContext(SettingsContext) || {}; diff --git a/ghost/admin-x-settings/src/components/providers/DataProvider.tsx b/ghost/admin-x-settings/src/components/providers/DataProvider.tsx new file mode 100644 index 0000000000..c92396046c --- /dev/null +++ b/ghost/admin-x-settings/src/components/providers/DataProvider.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import {SettingsProvider} from './SettingsProvider'; +import {UsersProvider} from './UsersProvider'; + +type DataProviderProps = { + children: React.ReactNode; +}; + +const DataProvider: React.FC = ({children}) => { + return ( + + + {children} + + + ); +}; + +export default DataProvider; \ No newline at end of file diff --git a/ghost/admin-x-settings/src/components/SettingsProvider.tsx b/ghost/admin-x-settings/src/components/providers/SettingsProvider.tsx similarity index 94% rename from ghost/admin-x-settings/src/components/SettingsProvider.tsx rename to ghost/admin-x-settings/src/components/providers/SettingsProvider.tsx index 662a80759d..0e125affd5 100644 --- a/ghost/admin-x-settings/src/components/SettingsProvider.tsx +++ b/ghost/admin-x-settings/src/components/providers/SettingsProvider.tsx @@ -1,6 +1,6 @@ import React, {createContext, useEffect, useState} from 'react'; -import {Setting} from '../types/api'; -import {getSettings, updateSettings} from '../utils/api'; +import {Setting} from '../../types/api'; +import {getSettings, updateSettings} from '../../utils/api'; // Define the Settings Context interface SettingsContextProps { diff --git a/ghost/admin-x-settings/src/components/providers/UsersProvider.tsx b/ghost/admin-x-settings/src/components/providers/UsersProvider.tsx new file mode 100644 index 0000000000..3cf1ab2f7e --- /dev/null +++ b/ghost/admin-x-settings/src/components/providers/UsersProvider.tsx @@ -0,0 +1,42 @@ +import React, {createContext, useEffect, useState} from 'react'; +import {User} from '../../types/api'; +import {getUsers} from '../../utils/api'; + +interface UsersContextProps { + users: User[]; +} + +interface UsersProviderProps { + children?: React.ReactNode; +} + +const UsersContext = createContext({ + users: [] +}); + +const UsersProvider: React.FC = ({children}) => { + const [users, setUsers] = useState ([]); + + useEffect(() => { + const fetchUsers = async (): Promise => { + try { + // get list of staff users from the API + const data = await getUsers(); + setUsers(data.users); + } catch (error) { + // Log error in API + } + }; + + fetchUsers(); + }, []); + + // Provide the settings and the saveSettings function to the children components + return ( + + {children} + + ); +}; + +export {UsersContext, UsersProvider}; diff --git a/ghost/admin-x-settings/src/components/settings/general/SocialAccounts.tsx b/ghost/admin-x-settings/src/components/settings/general/SocialAccounts.tsx index 5e45c865d9..0affadacdc 100644 --- a/ghost/admin-x-settings/src/components/settings/general/SocialAccounts.tsx +++ b/ghost/admin-x-settings/src/components/settings/general/SocialAccounts.tsx @@ -2,7 +2,7 @@ import React, {useContext, useState} from 'react'; import SettingGroup from '../../../admin-x-ds/settings/SettingGroup'; import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent'; import TextField from '../../../admin-x-ds/global/TextField'; -import {SettingsContext} from '../../SettingsProvider'; +import {SettingsContext} from '../../providers/SettingsProvider'; import {TSettingGroupStates} from '../../../admin-x-ds/settings/SettingGroup'; import {getSettingValue} from '../../../utils/helpers'; diff --git a/ghost/admin-x-settings/src/components/settings/general/Users.tsx b/ghost/admin-x-settings/src/components/settings/general/Users.tsx index 7d32476aa4..2ebc643ce2 100644 --- a/ghost/admin-x-settings/src/components/settings/general/Users.tsx +++ b/ghost/admin-x-settings/src/components/settings/general/Users.tsx @@ -7,14 +7,61 @@ import React from 'react'; import SettingGroup from '../../../admin-x-ds/settings/SettingGroup'; import TabView from '../../../admin-x-ds/global/TabView'; import UserDetailModal from './modals/UserDetailModal'; +import useStaffUsers from '../../../hooks/useStaffUsers'; -const Users: React.FC = () => { - const showInviteModal = () => { - NiceModal.show(InviteUserModal); +const Owner: React.FC = ({user}) => { + if (!user) { + return null; + } + return ( +
+ {user.name} — Owner + {user.email} +
+ ); +}; + +const UsersList: React.FC = ({users}) => { + const showDetailModal = () => { + NiceModal.show(UserDetailModal); }; - const showDetailModal = () => { - NiceModal.show(UserDetailModal); + if (!users || !users.length) { + return ( +
+

No users found

+
+ ); + } + + return ( + + {users.map((user: any) => { + return ( + } + detail={user.email} + hideActions={true} + id={`list-item-${user.id}`} + title={user.name} /> + ); + })} + + ); +}; + +const Users: React.FC = () => { + const { + ownerUser, + adminUsers, + editorUsers, + authorUsers, + contributorUsers + } = useStaffUsers(); + + const showInviteModal = () => { + NiceModal.show(InviteUserModal); }; const buttons = ( @@ -23,52 +70,35 @@ const Users: React.FC = () => { }} /> ); - const owner = ( -
- Cristofer Vaccaro — Owner - cristofer@example.com -
- ); - - const admins = ( - - } - detail='alena@press.com' - hideActions={true} - id='list-item-1' - title='Alena Press' /> - - ); - - const editors = ( - - } - detail='lydia@siphron.com' - hideActions={true} - id='list-item-1' - title='Lydia Siphron' /> - } - detail='james@korsgaard.com' - hideActions={true} - id='list-item-1' - title='James Korsgaard' /> - - ); - const tabs = [ - {id: 'users-admins', title: 'Administrators', contents: admins}, - {id: 'users-editors', title: 'Editors', contents: editors} + { + id: 'users-admins', + title: 'Administrators', + contents: () + }, + { + id: 'users-editors', + title: 'Editors', + contents: () + }, + { + id: 'users-authors', + title: 'Authors', + contents: () + }, + { + id: 'users-contributors', + title: 'Contributors', + contents: () + } ]; return ( - - {owner} + ); diff --git a/ghost/admin-x-settings/src/hooks/useSettingGroup.tsx b/ghost/admin-x-settings/src/hooks/useSettingGroup.tsx index e48d23f184..5d25ca7479 100644 --- a/ghost/admin-x-settings/src/hooks/useSettingGroup.tsx +++ b/ghost/admin-x-settings/src/hooks/useSettingGroup.tsx @@ -1,6 +1,6 @@ import React, {useEffect} from 'react'; import {Setting, SettingValue} from '../types/api'; -import {SettingsContext} from '../components/SettingsProvider'; +import {SettingsContext} from '../components/providers/SettingsProvider'; import {TSettingGroupStates} from '../admin-x-ds/settings/SettingGroup'; import {useContext, useReducer, useRef, useState} from 'react'; diff --git a/ghost/admin-x-settings/src/hooks/useStaffUsers.tsx b/ghost/admin-x-settings/src/hooks/useStaffUsers.tsx new file mode 100644 index 0000000000..6472e6a7b1 --- /dev/null +++ b/ghost/admin-x-settings/src/hooks/useStaffUsers.tsx @@ -0,0 +1,39 @@ +import {User} from '../types/api'; +import {UsersContext} from '../components/providers/UsersProvider'; +import {useContext} from 'react'; + +export type UsersHook = { + users: User[]; + ownerUser: User; + adminUsers: User[]; + editorUsers: User[]; + authorUsers: User[]; + contributorUsers: User[]; +}; + +function getUsersByRole(users: User[], role: string): User[] { + return users.filter((user) => { + return user.roles.find((userRole) => { + return userRole.name === role; + }); + }); +} + +const useStaffUsers = (): UsersHook => { + const {users} = useContext(UsersContext); + const ownerUser = getUsersByRole(users, 'Owner')[0] || null; + const adminUsers = getUsersByRole(users, 'Administrator'); + const editorUsers = getUsersByRole(users, 'Editor'); + const authorUsers = getUsersByRole(users, 'Author'); + const contributorUsers = getUsersByRole(users, 'Contributor'); + return { + users, + ownerUser, + adminUsers, + editorUsers, + authorUsers, + contributorUsers + }; +}; + +export default useStaffUsers; diff --git a/ghost/admin-x-settings/src/types/api.ts b/ghost/admin-x-settings/src/types/api.ts index eff2b6a963..89f6265b29 100644 --- a/ghost/admin-x-settings/src/types/api.ts +++ b/ghost/admin-x-settings/src/types/api.ts @@ -4,3 +4,43 @@ export type Setting = { key: string; value: SettingValue; } + +export type User = { + id: string; + name: string; + slug: string; + email: string; + profile_image: string; + cover_image: string|null; + bio: string; + website: string; + location: string; + facebook: string; + twitter: string; + accessibility: string|null; + status: string; + meta_title: string|null; + meta_description: string|null; + tour: string|null; + last_seen: string|null; + created_at: string; + updated_at: string; + comment_notifications: boolean; + free_member_signup_notification: boolean; + paid_subscription_canceled_notification: boolean; + paid_subscription_started_notification: boolean; + mention_notifications: boolean; + milestone_notifications: boolean; + roles: UserRole[]; + url: string; +} + +export type UserRoleType = 'Owner' | 'Administrator' | 'Editor' | 'Author' | 'Contributor'; + +export type UserRole = { + id: string; + name: UserRoleType; + description: string; + created_at: string; + updated_at: string; +}; diff --git a/ghost/admin-x-settings/src/utils/api.ts b/ghost/admin-x-settings/src/utils/api.ts index 20091f0d0d..6486b398d9 100644 --- a/ghost/admin-x-settings/src/utils/api.ts +++ b/ghost/admin-x-settings/src/utils/api.ts @@ -1,20 +1,40 @@ -import {Setting} from '../types/api'; +import {Setting, User} from '../types/api'; import {getGhostPaths} from './helpers'; -interface IQueryParams { +type ApiQueryParams = { + limit: string; + include: string; + [key: string]: string; +} + +type SettingApiQueryParams = { group: string; [key: string]: string; } -// Define the SettingsResponse type -export interface ISettingsResponse { - meta: any; +type Meta = { + pagination: { + page: number; + limit: number; + pages: number; + total: number; + next: number; + prev: number; + } +} + +export type SettingsResponseType = { + meta: Meta; settings: Setting[]; } +export type UsersResponseType = { + meta: Meta; + users: User[]; +} export async function getSettings() { const {apiRoot} = getGhostPaths(); - const queryParams: IQueryParams = {group: 'site,theme,private,members,portal,newsletter,email,amp,labs,slack,unsplash,views,firstpromoter,editor,comments,analytics,announcement,pintura'}; + const queryParams: SettingApiQueryParams = {group: 'site,theme,private,members,portal,newsletter,email,amp,labs,slack,unsplash,views,firstpromoter,editor,comments,analytics,announcement,pintura'}; const queryString = Object.keys(queryParams).map((key) => { return `${key}=${queryParams[key] || ''}`; }).join('&'); @@ -28,7 +48,7 @@ export async function getSettings() { mode: 'cors', credentials: 'include' }); - const data: ISettingsResponse = await response.json(); + const data: SettingsResponseType = await response.json(); return data; } @@ -51,6 +71,26 @@ export async function updateSettings(newSettings: Setting[]) { credentials: 'include' }); - const data: ISettingsResponse = await response.json(); + const data: SettingsResponseType = await response.json(); + return data; +} + +export async function getUsers() { + const {apiRoot} = getGhostPaths(); + const queryParams: ApiQueryParams = {limit: 'all', include: 'roles'}; + const queryString = Object.keys(queryParams).map((key) => { + return `${key}=${queryParams[key] || ''}`; + }).join('&'); + + const response = await fetch(`${apiRoot}/users/?${queryString}`, { + headers: { + 'app-pragma': 'no-cache', + 'x-ghost-version': '5.47' + }, + method: 'GET', + mode: 'cors', + credentials: 'include' + }); + const data: UsersResponseType = await response.json(); return data; }