Skip to content

Commit

Permalink
feat: add filter by resource to cms dashboard (#178)
Browse files Browse the repository at this point in the history
* feat: add filter by resource to cms dashboard

* refactor: add dynamic filter by resource

* feat(cms): setup zustand for state management

* chore(cms): fix ts build

* feat(cms): add login/register mutations to auth store ctx

* chore(cms): hide virtual fields from filter dropdown

* chore(example): remove auth roles and permissions from example app

* chore(example): remove auth roles and permissions from example app

* fix(tests): fix two-factor failing tests

Co-authored-by: Kati Frantz <bahdcoder@gmail.com>
  • Loading branch information
Placeholder30 and bahdcoder authored Nov 15, 2021
1 parent c35178f commit ad93113
Show file tree
Hide file tree
Showing 25 changed files with 576 additions and 251 deletions.
131 changes: 81 additions & 50 deletions examples/typescript/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import Path from 'path'
import { cms } from '@tensei/cms'
import { rest } from '@tensei/rest'
import { markdown } from '@tensei/mde'
import { auth } from '@tensei/auth'
import { graphql } from '@tensei/graphql'
import { auth, permission, role } from '@tensei/auth'
import { files, media } from '@tensei/media'

import {
tensei,
Expand All @@ -12,77 +11,109 @@ import {
resource,
text,
textarea,
dateTime,
integer,
slug,
array,
hasMany,
belongsTo,
boolean,
select
belongsToMany,
hasMany,
boolean
} from '@tensei/core'

export default tensei()
.resources([
resource('Post')
resource('Product')
.fields([
text('Title').rules('required'),
text('Name').rules('required'),
slug('Slug')
.creationRules('required', 'unique:slug')
.unique()
.from('Title'),
markdown('Description').creationRules('required', 'max:255'),
textarea('Content').nullable().rules('required'),
dateTime('Published At').creationRules('required'),
belongsTo('Category').alwaysLoad(),
array('Procedure')
.of('decimal')
.rules('min:3', 'max:10')
.creationRules('required', 'max:24'),
array('Prices')
.nullable()
.of('string')
.rules('max:10', 'min:2')
.creationRules('required', 'max:500')
.from('Name'),
textarea('Description').creationRules('required', 'max:255'),
integer('Price').rules('required'),
belongsToMany('Category'),
belongsToMany('Product Option'),
belongsToMany('Order Item'),
belongsToMany('Collection'),
belongsToMany('Review'),
files('Image')
])
.icon('library')
.displayField('Title'),
.displayField('Name'),
resource('Category')
.fields([
text('Name').notNullable().rules('required'),
textarea('Description'),
belongsTo('User').nullable(),
select('Specificity').options(['None', 'Some', 'All']),
hasMany('Post')
slug('Slug')
.creationRules('required', 'unique:slug')
.unique()
.from('Name'),
textarea('Description').nullable(),
belongsToMany('Product')
])
.displayField('Name')
.displayField('Name'),
resource('Collection')
.fields([
text('Name').notNullable().rules('required'),
slug('Slug')
.creationRules('required', 'unique:slug')
.unique()
.from('Name'),
textarea('Description').nullable(),
belongsToMany('Product')
])
.displayField('Name'),
resource('Review').fields([
text('Headline').rules('required'),
belongsTo('Customer').nullable(),
text('Name').nullable(),
text('Email').nullable(),
textarea('Content').rules('required'),
integer('Rating').rules('required', 'min:0', 'max:5'),
boolean('Approved').default(false).hideOnCreate().hideOnCreateApi(),
belongsTo('Product')
]),
resource('Order').fields([
integer('Total').rules('required'),
belongsTo('Customer'),
text('Stripe Checkout ID'),
belongsToMany('Product')
]),
resource('Order Item').fields([
integer('Quantity').min(0).rules('min:0', 'required'),
integer('Total').rules('required', 'min:0'),
belongsTo('Order').rules('required'),
belongsTo('Product').rules('required')
]),
resource('Option').fields([
text('Name').rules('Required'),
slug('Short name')
.creationRules('required', 'unique:slug')
.unique()
.from('Name')
]),
resource('Option Value').fields([
text('Name').rules('Required'),
slug('Short name')
.creationRules('required', 'unique:slug')
.unique()
.from('Name'),
belongsToMany('Option')
]),
resource('Product Option').fields([
belongsToMany('Option'),
belongsToMany('Option Value'),
belongsToMany('Product'),
files('Image')
])
])
.plugins([
welcome(),
cms().plugin(),
media().plugin(),
auth()
.teams()
.user('Customer')
.configureTokens({
accessTokenExpiresIn: 60 * 60 * 60 * 60 * 60
})
.roles([
role('Chief Marketer').permissions([
permission('Create Pages'),
permission('Delete Pages'),
permission('Update Pages'),
permission('Link Menu To Pages')
]),
role('Teacher').permissions([permission('Authorize Comments')])
])
.teamPermissions([permission('Create Article')])
.verifyEmails()
.setup(({ user }) => {
user.fields([
hasMany('Category'),
boolean('Accepted Terms And Conditions')
.rules('required')
.default(false)
])
})
.plugin(),
rest().plugin(),
graphql().plugin(),
Expand Down
50 changes: 49 additions & 1 deletion packages/cms/core.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Qs from 'qs'
import Axios from 'axios'
import Axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
import * as Lib from '@tensei/components'

// Form
Expand Down Expand Up @@ -87,6 +87,54 @@ class Core {
xsrfCookieName: 'x-csrf-token'
})

api = {
get: async (uri: string, config?: AxiosRequestConfig) => {
try {
const response = await this.client.get(uri, config)

return [response, null] as any
} catch (error) {
return [null, error] as any
}
},
post: async (uri: string, data?: any, config?: AxiosRequestConfig) => {
try {
const response = await this.client.post(uri, data, config)

return [response, null] as any
} catch (error) {
return [null, error] as any
}
},
put: async (uri: string, data?: any, config?: AxiosRequestConfig) => {
try {
const response = await this.client.put(uri, data, config)

return [response, null] as any
} catch (error) {
return [null, error] as any
}
},
patch: async (uri: string, data?: any, config?: AxiosRequestConfig) => {
try {
const response = await this.client.patch(uri, data, config)

return [response, null] as any
} catch (error) {
return [null, error] as any
}
},
delete: async (uri: string, config?: AxiosRequestConfig) => {
try {
const response = await this.client.delete(uri, config)

return [response, null] as any
} catch (error) {
return [null, error] as any
}
}
}

register = (fn: Lib.TenseiRegisterFunction) => {
this.hooks.push(fn)
}
Expand Down
17 changes: 8 additions & 9 deletions packages/cms/main.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React, { createContext, useState, useEffect } from 'react'
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import './core'
import './load-icons'
import { ThemeProvider as StyledThemeProvider } from 'styled-components'

import { TenseiCtxInterface, CmsRoute } from '@tensei/components'
import { CmsRoute } from '@tensei/components'
import { useAuthStore } from './store/auth'

import '@tensei/eui/dist/eui_theme_tensei_light.css'
import { TenseiCtx } from './pages/components/auth/context'
import { AuthRoutes } from './pages/components/auth/routes'
import { DashboardRoutes } from './pages/components/dashboard/routes'
import { useEuiTheme, EuiThemeProvider } from '@tensei/eui/lib/services/theme'
Expand Down Expand Up @@ -36,9 +36,10 @@ const extensions = {
const App: React.FunctionComponent = ({ children }) => {
const [booted, setBooted] = useState(false)
const [routes, setRoutes] = useState<CmsRoute[]>([])
const [user, setUser] = useState<TenseiCtxInterface['user']>(null as any)
const { euiTheme } = useEuiTheme<ThemeExtensions>()

const { user, setUser } = useAuthStore()

const value = {
user,
setUser,
Expand All @@ -55,11 +56,9 @@ const App: React.FunctionComponent = ({ children }) => {
}, [])

return (
<TenseiCtx.Provider value={value}>
<StyledThemeProvider theme={euiTheme}>
{booted ? children : 'Booting app...'}
</StyledThemeProvider>
</TenseiCtx.Provider>
<StyledThemeProvider theme={euiTheme}>
{booted ? children : 'Booting app...'}
</StyledThemeProvider>
)
}

Expand Down
3 changes: 2 additions & 1 deletion packages/cms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"moment": "^2.29.1",
"passport": "^0.4.1",
"passport-local": "^1.0.0",
"styled-components": "^5.3.3"
"styled-components": "^5.3.3",
"zustand": "^3.6.5"
}
}
11 changes: 0 additions & 11 deletions packages/cms/pages/components/auth/context/auth-context.tsx

This file was deleted.

1 change: 0 additions & 1 deletion packages/cms/pages/components/auth/context/index.ts

This file was deleted.

33 changes: 16 additions & 17 deletions packages/cms/pages/components/auth/guards/must-be-authenticated.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import React from 'react'
import { Redirect } from 'react-router-dom'

import { TenseiCtx } from '../context'
import { useAuthStore } from '../../../../store/auth'

export const MustBeAuthComponent = (Component: React.FC) => {
const { user } = useAuthStore()

const Comp = (props: any) => {
return (
<TenseiCtx.Consumer>
{({ user }) =>
user ? (
<Component {...props} />
) : (
<Redirect
to={
window.Tensei.state.registered
? window.Tensei.getPath('auth/login')
: window.Tensei.getPath('auth/register')
}
/>
)
}
</TenseiCtx.Consumer>
<>
{user ? (
<Component {...props} />
) : (
<Redirect
to={
window.Tensei.state.registered
? window.Tensei.getPath('auth/login')
: window.Tensei.getPath('auth/register')
}
/>
)}
</>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import React from 'react'
import { Redirect } from 'react-router-dom'

import { TenseiCtx } from '../context'
import { useAuthStore } from '../../../../store/auth'

export const MustBeNotAuthComponent = (Component: React.FC) => {
const { user } = useAuthStore()

const Comp = (props: any) => {
return (
<TenseiCtx.Consumer>
{({ user }) =>
!user ? (
<Component {...props} />
) : (
<Redirect to={window.Tensei.getPath('')} />
)
}
</TenseiCtx.Consumer>
<>
{!user ? (
<Component {...props} />
) : (
<Redirect to={window.Tensei.getPath('')} />
)}
</>
)
}

Expand Down
Loading

0 comments on commit ad93113

Please sign in to comment.