Skip to content

Commit

Permalink
feat: add organisation overview page
Browse files Browse the repository at this point in the history
  • Loading branch information
kabaros committed Dec 16, 2024
1 parent 00d75c3 commit 94b5dfb
Show file tree
Hide file tree
Showing 17 changed files with 225 additions and 25 deletions.
5 changes: 5 additions & 0 deletions client/src/AppHub.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ProtectedRoute from './components/auth/ProtectedRoute'
import Header from './components/Header/Header'
import Apps from './pages/Apps/Apps'
import AppView from './pages/AppView/AppView'
import OrganisationView from './pages/OrganisationView/OrganisationView'
import OrganisationInvitation from './pages/OrganisationInvitation/OrganisationInvitation'
import OrganisationInvitationCallback from './pages/OrganisationInvitationCallback/OrganisationInvitationCallback'
import UserView from './pages/UserView/UserView'
Expand All @@ -36,6 +37,10 @@ const AppHub = () => (
<Switch>
<Route exact path="/" component={Apps} />
<Route path="/app/:appId" component={AppView} />
<Route
path="/organisation/:organisationSlug/view"
component={OrganisationView}
/>
<ProtectedRoute
path="/user"
auth={Auth}
Expand Down
4 changes: 2 additions & 2 deletions client/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,15 +322,15 @@ export function addOrganisation({ name, email }) {
)
}

export function editOrganisation(id, { name, owner, email }) {
export function editOrganisation(id, { name, owner, email, description }) {
return apiV2.request(
`organisations/${id}`,
{
useAuth: true,
},
{
method: 'PATCH',
body: JSON.stringify({ name, owner, email }),
body: JSON.stringify({ name, owner, email, description }),
headers: {
'content-type': 'application/json',
},
Expand Down
13 changes: 12 additions & 1 deletion client/src/pages/AppView/AppView.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,23 @@ const HeaderSection = ({
logoSrc,
hasPlugin,
pluginType,
organisationSlug,
}) => (
<section
className={classnames(styles.appCardSection, styles.appCardHeader)}
>
<AppIcon src={logoSrc} />
<div>
<h2 className={styles.appCardName}>{appName}</h2>
<span className={styles.appCardDeveloper}>by {appDeveloper}</span>
<span className={styles.appCardDeveloper}>
by{' '}
<a
className={styles.link}
href={`/organisation/${organisationSlug}/view`}
>
{appDeveloper}
</a>
</span>
<div className={styles.appCardTypeContainer}>
<span className={styles.appCardType}>{appType}</span>
{hasPlugin && (
Expand All @@ -51,6 +60,7 @@ HeaderSection.propTypes = {
pluginType: PropTypes.string,
hasPlugin: PropTypes.bool,
logoSrc: PropTypes.string,
organisationSlug: PropTypes.string.isRequired,
}

const AboutSection = ({ appDescription, latestVersion, sourceUrl }) => (
Expand Down Expand Up @@ -131,6 +141,7 @@ const AppView = ({ match }) => {
<HeaderSection
appName={app.name}
appDeveloper={appDeveloper}
organisationSlug={app.developer?.organisation_slug}
appType={config.ui.appTypeToDisplayName[app.appType]}
logoSrc={logoSrc}
hasPlugin={app.hasPlugin}
Expand Down
7 changes: 5 additions & 2 deletions client/src/pages/AppView/AppView.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,15 @@
content: "";
}

.sourceUrl {
.link, .sourceUrl {
font-size: 14px;
color: var(--colors-blue700);
text-decoration: underline;
}

.sourceUrl:hover, .sourceUrl:focus {
.link:hover, .sourceUrl:hover,
.link:focus, .sourceUrl:focus {
color: var(--colors-grey900);
}


53 changes: 53 additions & 0 deletions client/src/pages/OrganisationView/OrganisationView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Card, CenteredContent, CircularLoader } from '@dhis2/ui'
import styles from './OrganisationView.module.css'
import { useQuery } from '../../api'
import AppCards from '../Apps/AppCards/AppCards'
import { relativeTimeFormat } from 'src/lib/relative-time-format'

const OrganisationView = ({ match }) => {
const { organisationSlug } = match.params

const { data: organisation } = useQuery(
`organisations/${organisationSlug}?includeApps=true&includeUsers=false`
)

if (!organisation) {
return (
<CenteredContent>
<CircularLoader large />
</CenteredContent>
)
}
return (
<div>
<Card className={styles.card}>
<div className={styles.header}>
<div className={styles.flex}>
<h2 className={styles.organisationName}>
{organisation?.name}
</h2>
</div>
<div className={styles.createdAt}>
<span>{organisation?.apps?.length} apps</span>
<span> | </span>
<span>
profile created{' '}
{relativeTimeFormat(
organisation?.createdAt ?? Date.now()
)}
</span>
</div>
{organisation?.description && (
<div className={styles.description}>
{organisation.description}
</div>
)}
</div>

<AppCards apps={organisation?.apps || []} />
</Card>
</div>
)
}

export default OrganisationView
34 changes: 34 additions & 0 deletions client/src/pages/OrganisationView/OrganisationView.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.card {
display: block !important;
max-width: 680px;
margin: auto;
padding: var(--spacers-dp16);
}

.header {
margin-bottom: var(--spacers-dp16);
}

.organisationName {
font-size: 24px;
font-weight: 700;
margin: 0;
margin-right: var(--spacers-dp8);
}

.createdAt, .description {
margin-top: var(--spacers-dp8);
margin-bottom: var(--spacers-dp8);

color: var(--colors-grey700);
font-size: 0.7em;
}
.description {
color: var(--colors-grey900);
line-height: 1.6em;

}

.flex {
display: flex;
}
9 changes: 8 additions & 1 deletion client/src/pages/UserApp/DetailsCard/DetailsCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,15 @@ const DetailsCard = ({ app, mutate }) => {
</div>
<div>
<h2 className={styles.detailsCardName}>{app.name}</h2>

<span className={styles.detailsCardDeveloper}>
by {appDeveloper}
by{' '}
<a
className={styles.link}
href={`/organisation/${app.developer?.organisation_slug}/view`}
>
{appDeveloper}
</a>
</span>
<span className={styles.detailsCardType}>{appType}</span>
{app.hasPlugin && (
Expand Down
4 changes: 2 additions & 2 deletions client/src/pages/UserApp/DetailsCard/DetailsCard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@
max-width: 640px;
}

.sourceUrl {
.link, .sourceUrl {
color: var(--colors-blue700);
text-decoration: underline;
}

.sourceUrl:hover, .sourceUrl:focus {
.link:hover, .link:focus, .sourceUrl:hover, .sourceUrl:focus {
color: var(--colors-grey900);
}
21 changes: 18 additions & 3 deletions client/src/pages/UserOrganisation/Modals/EditNameModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ModalContent,
ReactFinalForm,
InputFieldFF,
TextAreaFieldFF,
hasValue,
composeValidators,
email,
Expand All @@ -19,13 +20,18 @@ const EditNameModal = ({ organisation, mutate, onClose }) => {
const successAlert = useSuccessAlert()
const errorAlert = useErrorAlert()

const handleSubmit = async ({ name, email }) => {
const handleSubmit = async ({ name, email, description }) => {
try {
await api.editOrganisation(organisation.id, { name, email })
await api.editOrganisation(organisation.id, {
name,
email,
description,
})
mutate({
...organisation,
name,
email,
description,
})
successAlert.show({
message: `Successfully updated organisation name to ${name}`,
Expand Down Expand Up @@ -64,6 +70,15 @@ const EditNameModal = ({ organisation, mutate, onClose }) => {
className={styles.field}
validate={composeValidators(hasValue, email)}
/>
<ReactFinalForm.Field
required
name="description"
label="Organisation description"
placeholder="Enter a description"
component={TextAreaFieldFF}
initialValue={organisation.description}
className={styles.field}
/>
<ButtonStrip end>
<Button onClick={onClose} disabled={submitting}>
Cancel
Expand All @@ -73,7 +88,7 @@ const EditNameModal = ({ organisation, mutate, onClose }) => {
primary
disabled={!valid || submitting}
>
Update name
Update
</Button>
</ButtonStrip>
</form>
Expand Down
5 changes: 4 additions & 1 deletion client/src/pages/UserOrganisation/UserOrganisation.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,14 @@ const UserOrganisation = ({ match, user }) => {
secondary
onClick={editNameModal.show}
>
Edit name
Edit
</Button>
</>
)}
</div>
<div className={styles.description}>
{organisation.description || 'no description'}
</div>
<div className={styles.createdAt}>
Created{' '}
<span title={new Date(organisation.createdAt).toString()}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
margin-right: var(--spacers-dp8);
}

.createdAt {
.description {
font-size: 0.8em;
}

.createdAt, .description {
margin-top: var(--spacers-dp8);
color: var(--colors-grey800);
}
Expand Down
11 changes: 11 additions & 0 deletions server/migrations/20241210123044_organisation_description.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
exports.up = async (knex) => {
await knex.schema.table('organisation', (table) => {
table.string('description', 1000).nullable()
})
}

exports.down = async (knex) => {
await knex.schema.table('organisation', (table) => {
table.dropColumn('description')
})
}
1 change: 1 addition & 0 deletions server/src/models/v1/out/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ module.exports = Joi.object().keys({
address: Joi.string().allow(''),
email: Joi.string().email(),
organisation: Joi.string(),
organisation_slug: Joi.string(),
})
16 changes: 15 additions & 1 deletion server/src/models/v2/Organisation.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
const joi = require('../../utils/CustomJoi')
const User = require('./User')
const { definition: defaultDefinition } = require('./Default')
const { createDefaultValidator } = require('./helpers')
const User = require('./User')

const partialApp = joi.object().keys({
type: joi.string(),
createdAt: joi.number(),
description: joi.string().allow(''),
images: joi.any(),
id: joi.string(),
app_id: joi.string(),
name: joi.string(),
organisation: joi.any(),
hasPlugin: joi.boolean().allow(null, false),
})

const definition = defaultDefinition
.append({
name: joi.string().max(100),
email: joi.string().email().allow(null),
description: joi.string().allow(null),
slug: joi.string(),
owner: joi.string().guid({ version: 'uuidv4' }),
users: joi.array().items(User.definition),
apps: joi.array().items(partialApp.options({ allowUnknown: false })),
})
.alter({
db: (s) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const convertDbAppViewRowToAppApiV1Object = (app) => ({
address: '',
email: app.contact_email,
organisation: app.organisation,
organisation_slug: app.organisation_slug,
},

//TODO: can we use developer_email here ? previous it was oauth token|id
Expand Down
Loading

0 comments on commit 94b5dfb

Please sign in to comment.