Skip to content

Commit

Permalink
feat(cms): add administration panel settings page
Browse files Browse the repository at this point in the history
- add settings page navigation
- add create / read / update / delete admin users
- add cms settings roles and permissions
  • Loading branch information
Frantz Kati committed Jan 15, 2021
1 parent 72c23a3 commit b4e2d57
Show file tree
Hide file tree
Showing 28 changed files with 2,072 additions and 116 deletions.
11 changes: 11 additions & 0 deletions cloud.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Tensei Cloud is highly inspired by Laravel Vapor.

Stages of deployment
====================

1. Developer runs yarn tensei link <app-ID> locally.
2. Developer runs yarn tensei deploy. This sends the whole tensei project to our cloud.
3. TC runs npm install and npm run build in the cloud.
4. TC runs yarn tensei boot. This boots up the application by running all boot methods on plugins that need to make database queries.
For example, run database sync will be done during this boot. Setting up admins and permissions will be done during this boot. That way, at run time, all we do is establish the database connection.

18 changes: 16 additions & 2 deletions packages/cms/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
ToastOptions,
ResourceContract,
TenseiCtxInterface,
TenseiRegisterFunction
TenseiRegisterFunction,
CmsRoute
} from '@tensei/components'

// Form
Expand Down Expand Up @@ -108,7 +109,8 @@ class Core {
Text: IndexText,
Date: DetailDate,
DateTime: DetailDate,
Timestamp: DetailDate
Timestamp: DetailDate,
Boolean: DetailBoolean
},
detail: {
ID: DetailID,
Expand All @@ -125,6 +127,17 @@ class Core {
}
}

routes: CmsRoute[] = []

route = (route: CmsRoute) => {
this.routes.push({
...route,
settings: route.settings || false,
group: route.group || 'Global Settings',
requiredPermissions: route.requiredPermissions || []
})
}

ctx: TenseiCtxInterface = {} as any

client = Axios.create({
Expand Down Expand Up @@ -162,6 +175,7 @@ class Core {
if (this.state.admin) {
this.ctx.setUser(this.state.admin)
}
this.ctx.setRoutes([...this.ctx.routes, ...this.routes])
this.ctx.setBooted(true)
}

Expand Down
4 changes: 4 additions & 0 deletions packages/cms/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,7 @@
.react-select__multi-value__remove svg {
@apply hover:text-tensei-gray-800 !important;
}

.permissions-accordion-button:focus + .permissions-accordion-panel {
@apply border-b border-l border-r border-tensei-primary;
}
51 changes: 34 additions & 17 deletions packages/cms/form/ManyToMany/ManyToMany.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Qs from 'qs'
import { Link } from 'react-router-dom'
import AsyncSelect from 'react-select/async'
import React, { useEffect, useState } from 'react'
import {
Expand All @@ -12,9 +13,7 @@ const ManyToMany: React.FC<FormComponentProps> = ({
field,
name,
id,
value,
onChange,
error,
resource,
editing,
editingId
Expand All @@ -27,7 +26,11 @@ const ManyToMany: React.FC<FormComponentProps> = ({
r => r.name === field.name
)

if (!relatedResource) {
const relationField = resource.fields.find(
field => field.name === relatedResource?.name
)

if (!relatedResource || !relationField) {
return null
}

Expand All @@ -48,7 +51,7 @@ const ManyToMany: React.FC<FormComponentProps> = ({
}))
}

parameters.fields = `id,${relatedResource.displayFieldSnakeCase}`
parameters.fields = `id,${relatedResource.displayFieldSnakeCase},${relatedResource.secondaryDisplayFieldSnakeCase}`

parameters.page = 1
parameters.per_page = relatedResource.perPageOptions[0] || 10
Expand All @@ -61,13 +64,18 @@ const ManyToMany: React.FC<FormComponentProps> = ({
window.Tensei.client
.get(
`${resource.slug}/${editingId}/${
relatedResource.slug
relationField.inputName
}?${getQuery()}`
)
.then(({ data }) => {
setDefaultValue(
data.data.map((row: AbstractData) => ({
label: row[relatedResource.displayFieldSnakeCase],
label:
row[relatedResource.displayFieldSnakeCase] ||
row[
relatedResource
.secondaryDisplayFieldSnakeCase
],
value: row.id
}))
)
Expand All @@ -82,17 +90,6 @@ const ManyToMany: React.FC<FormComponentProps> = ({
}
}, [])

console.log(
'@@@@@@@@',
'Showing only the first ',
relatedResource.perPageOptions[0] || 10,
relatedResource.label.toLowerCase(),
'There are ',
hiddenRows,
' more ',
relatedResource.label.toLowerCase()
)

return (
<>
<Label id={id} label={relatedResource.label} />
Expand Down Expand Up @@ -123,6 +120,10 @@ const ManyToMany: React.FC<FormComponentProps> = ({
row[
relatedResource
.displayFieldSnakeCase
] ||
row[
relatedResource
.secondaryDisplayFieldSnakeCase
]
}))
)
Expand All @@ -137,6 +138,22 @@ const ManyToMany: React.FC<FormComponentProps> = ({
/>
</div>
)}
{!loadingDefault && hiddenRows !== 0 && editing ? (
<p className="text-tensei-primary font-semibold italic text-sm mt-3">
Showing only the first{' '}
{relatedResource.perPageOptions[0] || 10}{' '}
{relatedResource.label.toLowerCase()}.{' '}
<Link
className="border-b border-tensei-primary"
to={window.Tensei.getPath(
`resources/${resource.slug}/${editingId}`
)}
>
View the remaining {hiddenRows}{' '}
{relatedResource.label.toLowerCase()}.
</Link>
</p>
) : null}
</>
)
}
Expand Down
4 changes: 3 additions & 1 deletion packages/cms/form/Slug/Slug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const generateRandomString = () => {
return result
}

const shortSlug = generateRandomString()

const Slug: React.FC<FormComponentProps> = ({
field,
name,
Expand All @@ -40,7 +42,7 @@ const Slug: React.FC<FormComponentProps> = ({
? slugify(`${slug} ${Dayjs().format('YYYY-MM-DD')}`)
: ''
case 'random':
return slug ? slugify(`${slug} ${generateRandomString()}`) : ''
return slug ? slugify(`${slug} ${shortSlug}`) : ''
default:
return slugify(slug)
}
Expand Down
25 changes: 19 additions & 6 deletions packages/cms/index/ID/ID.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,25 @@ import IDDetail from '../../detail/ID'

import { IndexComponentProps } from '@tensei/components'

const ID: React.FC<IndexComponentProps> = ({
value,
values,
field,
resource
}) => {
const ID: React.FC<
IndexComponentProps & {
noLink?: boolean
}
> = ({ value, values, field, resource, noLink }) => {
const detail = (
<IDDetail
value={value}
field={field}
values={values}
resource={resource}
className="transition duration-150 hover:bg-opacity-20"
/>
)

if (noLink) {
return detail
}

return (
<Link
className="cursor-pointer"
Expand Down
1 change: 1 addition & 0 deletions packages/cms/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BrowserRouter } from 'react-router-dom'
import './core'

// Styles
import '@reach/accordion/styles.css'
import 'toastedjs/dist/toasted.min.css'
import '@tensei/components/styles/pulse.css'
import '@tensei/components/styles/flatpickr.css'
Expand Down
3 changes: 3 additions & 0 deletions packages/cms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@
},
"dependencies": {
"@apollo/client": "^3.3.4",
"@reach/accordion": "^0.12.1",
"@tailwindcss/aspect-ratio": "^0.2.0",
"@tailwindcss/forms": "^0.2.1",
"@tailwindcss/typography": "^0.3.1",
"@tailwindcss/ui": "^0.7.2",
"@types/axios": "^0.14.0",
"@types/classnames": "^2.2.11",
"@types/qs": "^6.9.5",
"@types/react-gravatar": "^2.6.8",
"@types/react-paginate": "^6.2.1",
"@types/react-select": "^3.1.2",
"@types/speakingurl": "^13.0.2",
Expand All @@ -66,6 +68,7 @@
"copyfiles": "^2.4.1",
"graphql": "^15.4.0",
"qs": "^6.9.4",
"react-gravatar": "^2.6.3",
"react-paginate": "^7.0.0",
"react-router-dom": "^5.2.0",
"react-select": "^3.1.1",
Expand Down
45 changes: 35 additions & 10 deletions packages/cms/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Gravatar from 'react-gravatar'
import React, { useState } from 'react'
import { Route, Switch } from 'react-router-dom'
import { Transition, Menu } from '@tensei/components'
import { Route, Switch, Link } from 'react-router-dom'

import Nav from '../components/Nav'

Expand Down Expand Up @@ -167,10 +168,13 @@ const Dashboard: React.FC<DashboardProps> = () => {
<span className="sr-only">
Open user menu
</span>
<img
<Gravatar
className="h-10 w-10 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
email={
window.Tensei
.state.admin
.email
}
/>
</Menu.Button>
</span>
Expand Down Expand Up @@ -208,17 +212,39 @@ const Dashboard: React.FC<DashboardProps> = () => {
{({
active
}) => (
<a
href="#account-settings"
<Link
to={window.Tensei.getPath(
'settings'
)}
className={`${
active
? 'bg-tensei-gray-100'
: ''
} flex justify-between w-full px-4 py-3 leading-5 text-left`}
>
Account
settings
</a>
Profile
</Link>
)}
</Menu.Item>
</div>

<div>
<Menu.Item>
{({
active
}) => (
<Link
to={window.Tensei.getPath(
'settings'
)}
className={`${
active
? 'bg-tensei-gray-100'
: ''
} flex justify-between w-full px-4 py-3 leading-5 text-left`}
>
Settings
</Link>
)}
</Menu.Item>
</div>
Expand Down Expand Up @@ -297,7 +323,6 @@ const Dashboard: React.FC<DashboardProps> = () => {
/>

<Route
exact
component={Settings}
path={window.Tensei.getPath('settings')}
/>
Expand Down
10 changes: 9 additions & 1 deletion packages/cms/pages/Resource/Resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ const Resource: React.FC<ResourceProps> = ({

const fields = resource.fields.filter(field => field.showOnIndex)

const relatedField = baseResource.fields.find(
f => f.name === relatedResource?.name
)

if (!relatedField && relatedResource) {
return null
}

const searchableFields = resource.fields.filter(field => field.isSearchable)

const getDefaultParametersFromSearch = () => {
Expand Down Expand Up @@ -125,7 +133,7 @@ const Resource: React.FC<ResourceProps> = ({
window.Tensei.client
.get(
relatedResource
? `${baseResource.slug}/${detailId}/${relatedResource.slug}?${query}`
? `${baseResource.slug}/${detailId}/${relatedField?.inputName}?${query}`
: `${slug}?${query}`
)
.then(({ data: payload }) => {
Expand Down
Loading

0 comments on commit b4e2d57

Please sign in to comment.