setup settings cell and modified a query and acell to return a specific value

This commit is contained in:
Graeme Ross 2024-10-20 15:49:33 +01:00
parent 3f3df221c8
commit 6221af88cf
24 changed files with 2876 additions and 120 deletions

View File

@ -0,0 +1,8 @@
-- CreateTable
CREATE TABLE "Setting" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"enabled" BOOLEAN NOT NULL,
"group" TEXT NOT NULL,
"name" TEXT NOT NULL,
"value" TEXT NOT NULL
);

View File

@ -68,3 +68,11 @@ model Role {
User User? @relation(fields: [userId], references: [id]) User User? @relation(fields: [userId], references: [id])
userId String? userId String?
} }
model Setting {
id Int @id @default(autoincrement())
enabled Boolean
group String
name String
value String
}

View File

@ -13,11 +13,13 @@ export const schema = gql`
directive @requireAuth(roles: [String]) on FIELD_DEFINITION directive @requireAuth(roles: [String]) on FIELD_DEFINITION
` `
type RequireAuthValidate = ValidatorDirectiveFunc<{ roles?: string[] }> type RequireAuthValidate = ValidatorDirectiveFunc<{
roles?: string[]
}>
const validate: RequireAuthValidate = ({ directiveArgs }) => { const validate: RequireAuthValidate = ({ context, directiveArgs }) => {
const { roles } = directiveArgs const { roles } = directiveArgs
applicationRequireAuth({ roles }) applicationRequireAuth({ context, roles })
} }
const requireAuth = createValidatorDirective(schema, validate) const requireAuth = createValidatorDirective(schema, validate)

View File

@ -0,0 +1,35 @@
export const schema = gql`
type Setting {
id: Int!
enabled: Boolean!
group(group: String = "default"): String!
name(name: String): String!
value: String!
}
type Query {
settings: [Setting!]! @requireAuth
setting(id: Int!): Setting @skipAuth
value(name: String, group: String): [Setting!] @skipAuth
}
input CreateSettingInput {
enabled: Boolean!
group: String!
name: String!
value: String!
}
input UpdateSettingInput {
enabled: Boolean
group: String
name: String
value: String
}
type Mutation {
createSetting(input: CreateSettingInput!): Setting! @requireAuth
updateSetting(id: Int!, input: UpdateSettingInput!): Setting! @requireAuth
deleteSetting(id: Int!): Setting! @requireAuth
}
`

View File

@ -45,7 +45,7 @@ export const getCurrentUser = async (session: Decoded) => {
* *
* @returns {boolean} - If the currentUser is authenticated * @returns {boolean} - If the currentUser is authenticated
*/ */
export const isAuthenticated = (): boolean => { export const isAuthenticated = (context?): boolean => {
return !!context.currentUser return !!context.currentUser
} }
@ -110,8 +110,14 @@ export const hasRole = (roles: AllowedRoles): boolean => {
* *
* @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples * @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples
*/ */
export const requireAuth = ({ roles }: { roles?: AllowedRoles } = {}) => { export const requireAuth = ({
if (!isAuthenticated()) { context,
roles,
}: {
context
roles?: AllowedRoles
}) => {
if (!isAuthenticated(context)) {
throw new AuthenticationError("You don't have permission to do that.") throw new AuthenticationError("You don't have permission to do that.")
} }

View File

@ -0,0 +1,15 @@
import type { Prisma, Setting } from '@prisma/client'
import type { ScenarioData } from '@redwoodjs/testing/api'
export const standard = defineScenario<Prisma.SettingCreateArgs>({
setting: {
one: {
data: { enabled: true, group: 'String', name: 'String', value: 'String' },
},
two: {
data: { enabled: true, group: 'String', name: 'String', value: 'String' },
},
},
})
export type StandardScenario = ScenarioData<Setting, 'setting'>

View File

@ -0,0 +1,65 @@
import type { Setting } from '@prisma/client'
import {
settings,
setting,
createSetting,
updateSetting,
deleteSetting,
} from './settings'
import type { StandardScenario } from './settings.scenarios'
// Generated boilerplate tests do not account for all circumstances
// and can fail without adjustments, e.g. Float.
// Please refer to the RedwoodJS Testing Docs:
// https://redwoodjs.com/docs/testing#testing-services
// https://redwoodjs.com/docs/testing#jest-expect-type-considerations
describe('settings', () => {
scenario('returns all settings', async (scenario: StandardScenario) => {
const result = await settings()
expect(result.length).toEqual(Object.keys(scenario.setting).length)
})
scenario('returns a single setting', async (scenario: StandardScenario) => {
const result = await setting({ id: scenario.setting.one.id })
expect(result).toEqual(scenario.setting.one)
})
scenario('creates a setting', async () => {
const result = await createSetting({
input: {
enabled: true,
group: 'String',
name: 'String',
value: 'String',
},
})
expect(result.enabled).toEqual(true)
expect(result.group).toEqual('String')
expect(result.name).toEqual('String')
expect(result.value).toEqual('String')
})
scenario('updates a setting', async (scenario: StandardScenario) => {
const original = (await setting({ id: scenario.setting.one.id })) as Setting
const result = await updateSetting({
id: original.id,
input: { enabled: false },
})
expect(result.enabled).toEqual(false)
})
scenario('deletes a setting', async (scenario: StandardScenario) => {
const original = (await deleteSetting({
id: scenario.setting.one.id,
})) as Setting
const result = await setting({ id: original.id })
expect(result).toEqual(null)
})
})

View File

@ -0,0 +1,44 @@
import type { QueryResolvers, MutationResolvers } from 'types/graphql'
import { db } from 'src/lib/db'
export const settings: QueryResolvers['settings'] = () => {
return db.setting.findMany()
}
export const setting: QueryResolvers['setting'] = ({ id }) => {
return db.setting.findUnique({
where: { id },
})
}
export const value: QueryResolvers['value'] = ({ name, group }) => {
const values = db.setting.findMany({
where: { AND: [{ name: name }, { group: group }] },
})
return values
}
export const createSetting: MutationResolvers['createSetting'] = ({
input,
}) => {
return db.setting.create({
data: input,
})
}
export const updateSetting: MutationResolvers['updateSetting'] = ({
id,
input,
}) => {
return db.setting.update({
data: input,
where: { id },
})
}
export const deleteSetting: MutationResolvers['deleteSetting'] = ({ id }) => {
return db.setting.delete({
where: { id },
})
}

View File

@ -10,6 +10,8 @@
"@redwoodjs/auth-dbauth-setup": "8.4.0", "@redwoodjs/auth-dbauth-setup": "8.4.0",
"@redwoodjs/core": "8.4.0", "@redwoodjs/core": "8.4.0",
"@redwoodjs/project-config": "8.4.0", "@redwoodjs/project-config": "8.4.0",
"@redwoodjs/realtime": "8.4.0",
"@redwoodjs/studio": "12",
"prettier-plugin-tailwindcss": "^0.6.8" "prettier-plugin-tailwindcss": "^0.6.8"
}, },
"eslintConfig": { "eslintConfig": {

View File

@ -7,7 +7,7 @@
// 'src/pages/HomePage/HomePage.js' -> HomePage // 'src/pages/HomePage/HomePage.js' -> HomePage
// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage // 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage
import { Router, Route, Set } from '@redwoodjs/router' import { Router, Route, PrivateSet, Set } from '@redwoodjs/router'
import ClientLayout from 'src/layouts/ClientLayout/ClientLayout' import ClientLayout from 'src/layouts/ClientLayout/ClientLayout'
import ScaffoldLayout from 'src/layouts/ScaffoldLayout' import ScaffoldLayout from 'src/layouts/ScaffoldLayout'
@ -17,34 +17,42 @@ import { useAuth } from './auth'
const Routes = () => { const Routes = () => {
return ( return (
<Router useAuth={useAuth}> <Router useAuth={useAuth}>
<PrivateSet unauthenticated="home">
<Set wrap={ScaffoldLayout} title="Settings" titleTo="settings" buttonLabel="New Setting" buttonTo="newSetting">
<Route path="/settings/new" page={SettingNewSettingPage} name="newSetting" />
<Route path="/settings/{id:Int}/edit" page={SettingEditSettingPage} name="editSetting" />
<Route path="/settings/{id:Int}" page={SettingSettingPage} name="setting" />
<Route path="/settings" page={SettingSettingsPage} name="settings" />
</Set>
<Set wrap={ScaffoldLayout} title="Roles" titleTo="roles" buttonLabel="New Role" buttonTo="newRole">
<Route path="/roles/new" page={RoleNewRolePage} name="newRole" />
<Route path="/roles/{id}/edit" page={RoleEditRolePage} name="editRole" />
<Route path="/roles/{id}" page={RoleRolePage} name="role" />
<Route path="/roles" page={RoleRolesPage} name="roles" />
</Set>
<Set wrap={ScaffoldLayout} title="Users" titleTo="users" buttonLabel="New User" buttonTo="newUser">
<Route path="/users/new" page={UserNewUserPage} name="newUser" />
<Route path="/users/{id}/edit" page={UserEditUserPage} name="editUser" />
<Route path="/users/{id}" page={UserUserPage} name="user" />
<Route path="/users" page={UserUsersPage} name="users" />
</Set>
<Set wrap={ScaffoldLayout} title="ContactAddresses" titleTo="contactAddresses" buttonLabel="New ContactAddress" buttonTo="newContactAddress">
<Route path="/contact-addresses/new" page={ContactAddressNewContactAddressPage} name="newContactAddress" />
<Route path="/contact-addresses/{id}/edit" page={ContactAddressEditContactAddressPage} name="editContactAddress" />
<Route path="/contact-addresses/{id}" page={ContactAddressContactAddressPage} name="contactAddress" />
<Route path="/contact-addresses" page={ContactAddressContactAddressesPage} name="contactAddresses" />
</Set>
<Set wrap={ScaffoldLayout} title="Accounts" titleTo="accounts" buttonLabel="New Account" buttonTo="newAccount">
<Route path="/accounts/new" page={AccountNewAccountPage} name="newAccount" />
<Route path="/accounts/{id}/edit" page={AccountEditAccountPage} name="editAccount" />
<Route path="/accounts/{id}" page={AccountAccountPage} name="account" />
<Route path="/accounts" page={AccountAccountsPage} name="accounts" />
</Set>
</PrivateSet>
<Route path="/login" page={LoginPage} name="login" /> <Route path="/login" page={LoginPage} name="login" />
<Route path="/signup" page={SignupPage} name="signup" /> <Route path="/signup" page={SignupPage} name="signup" />
<Route path="/forgot-password" page={ForgotPasswordPage} name="forgotPassword" /> <Route path="/forgot-password" page={ForgotPasswordPage} name="forgotPassword" />
<Route path="/reset-password" page={ResetPasswordPage} name="resetPassword" /> <Route path="/reset-password" page={ResetPasswordPage} name="resetPassword" />
<Set wrap={ScaffoldLayout} title="Roles" titleTo="roles" buttonLabel="New Role" buttonTo="newRole">
<Route path="/roles/new" page={RoleNewRolePage} name="newRole" />
<Route path="/roles/{id}/edit" page={RoleEditRolePage} name="editRole" />
<Route path="/roles/{id}" page={RoleRolePage} name="role" />
<Route path="/roles" page={RoleRolesPage} name="roles" />
</Set>
<Set wrap={ScaffoldLayout} title="Users" titleTo="users" buttonLabel="New User" buttonTo="newUser">
<Route path="/users/new" page={UserNewUserPage} name="newUser" />
<Route path="/users/{id}/edit" page={UserEditUserPage} name="editUser" />
<Route path="/users/{id}" page={UserUserPage} name="user" />
<Route path="/users" page={UserUsersPage} name="users" />
</Set>
<Set wrap={ScaffoldLayout} title="ContactAddresses" titleTo="contactAddresses" buttonLabel="New ContactAddress" buttonTo="newContactAddress">
<Route path="/contact-addresses/new" page={ContactAddressNewContactAddressPage} name="newContactAddress" />
<Route path="/contact-addresses/{id}/edit" page={ContactAddressEditContactAddressPage} name="editContactAddress" />
<Route path="/contact-addresses/{id}" page={ContactAddressContactAddressPage} name="contactAddress" />
<Route path="/contact-addresses" page={ContactAddressContactAddressesPage} name="contactAddresses" />
</Set>
<Set wrap={ScaffoldLayout} title="Accounts" titleTo="accounts" buttonLabel="New Account" buttonTo="newAccount">
<Route path="/accounts/new" page={AccountNewAccountPage} name="newAccount" />
<Route path="/accounts/{id}/edit" page={AccountEditAccountPage} name="editAccount" />
<Route path="/accounts/{id}" page={AccountAccountPage} name="account" />
<Route path="/accounts" page={AccountAccountsPage} name="accounts" />
</Set>
<Set wrap={ClientLayout}> <Set wrap={ClientLayout}>
<Route path="/home" page={HomePage} name="home" /> <Route path="/home" page={HomePage} name="home" />
<Route path="/hero" page={HeroPage} name="hero" /> <Route path="/hero" page={HeroPage} name="hero" />

View File

@ -3,13 +3,17 @@ import { Link, routes } from '@redwoodjs/router'
import { useAuth } from 'src/auth' import { useAuth } from 'src/auth'
import ThemeChanger from 'src/components/ThemeChanger/ThemeChanger' import ThemeChanger from 'src/components/ThemeChanger/ThemeChanger'
import SettingValue from '../Setting/SettingValue/SettingValue'
const HeaderBar = () => { const HeaderBar = () => {
const { logOut } = useAuth() const { logOut } = useAuth()
return ( return (
<div className="navbar bg-base-100"> <div className="navbar bg-base-100">
<div className="flex-1"> <div className="flex-1">
<a className="btn btn-ghost text-xl">Pendantator</a> <Link className="btn btn-ghost text-xl" to="${routes.home()}">
<SettingValue name="title" />
</Link>
</div> </div>
<div className="flex-none"> <div className="flex-none">
<ul className="menu menu-horizontal px-1"> <ul className="menu menu-horizontal px-1">

View File

@ -0,0 +1,89 @@
import type {
EditSettingById,
UpdateSettingInput,
UpdateSettingMutationVariables,
} from 'types/graphql'
import { navigate, routes } from '@redwoodjs/router'
import type {
CellSuccessProps,
CellFailureProps,
TypedDocumentNode,
} from '@redwoodjs/web'
import { useMutation } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/toast'
import SettingForm from 'src/components/Setting/SettingForm'
export const QUERY: TypedDocumentNode<EditSettingById> = gql`
query EditSettingById($id: Int!) {
setting: setting(id: $id) {
id
enabled
group
name
value
}
}
`
const UPDATE_SETTING_MUTATION: TypedDocumentNode<
EditSettingById,
UpdateSettingMutationVariables
> = gql`
mutation UpdateSettingMutation($id: Int!, $input: UpdateSettingInput!) {
updateSetting(id: $id, input: $input) {
id
enabled
group
name
value
}
}
`
export const Loading = () => <div>Loading...</div>
export const Failure = ({ error }: CellFailureProps) => (
<div className="rw-cell-error">{error?.message}</div>
)
export const Success = ({ setting }: CellSuccessProps<EditSettingById>) => {
const [updateSetting, { loading, error }] = useMutation(
UPDATE_SETTING_MUTATION,
{
onCompleted: () => {
toast.success('Setting updated')
navigate(routes.settings())
},
onError: (error) => {
toast.error(error.message)
},
}
)
const onSave = (
input: UpdateSettingInput,
id: EditSettingById['setting']['id']
) => {
updateSetting({ variables: { id, input } })
}
return (
<div className="rw-segment">
<header className="rw-segment-header">
<h2 className="rw-heading rw-heading-secondary">
Edit Setting {setting?.id}
</h2>
</header>
<div className="rw-segment-main">
<SettingForm
setting={setting}
onSave={onSave}
error={error}
loading={loading}
/>
</div>
</div>
)
}

View File

@ -0,0 +1,55 @@
import type {
CreateSettingMutation,
CreateSettingInput,
CreateSettingMutationVariables,
} from 'types/graphql'
import { navigate, routes } from '@redwoodjs/router'
import { useMutation } from '@redwoodjs/web'
import type { TypedDocumentNode } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/toast'
import SettingForm from 'src/components/Setting/SettingForm'
const CREATE_SETTING_MUTATION: TypedDocumentNode<
CreateSettingMutation,
CreateSettingMutationVariables
> = gql`
mutation CreateSettingMutation($input: CreateSettingInput!) {
createSetting(input: $input) {
id
}
}
`
const NewSetting = () => {
const [createSetting, { loading, error }] = useMutation(
CREATE_SETTING_MUTATION,
{
onCompleted: () => {
toast.success('Setting created')
navigate(routes.settings())
},
onError: (error) => {
toast.error(error.message)
},
}
)
const onSave = (input: CreateSettingInput) => {
createSetting({ variables: { input } })
}
return (
<div className="rw-segment">
<header className="rw-segment-header">
<h2 className="rw-heading rw-heading-secondary">New Setting</h2>
</header>
<div className="rw-segment-main">
<SettingForm onSave={onSave} loading={loading} error={error} />
</div>
</div>
)
}
export default NewSetting

View File

@ -0,0 +1,98 @@
import type {
DeleteSettingMutation,
DeleteSettingMutationVariables,
FindSettingById,
} from 'types/graphql'
import { Link, routes, navigate } from '@redwoodjs/router'
import { useMutation } from '@redwoodjs/web'
import type { TypedDocumentNode } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/toast'
import { checkboxInputTag } from 'src/lib/formatters'
const DELETE_SETTING_MUTATION: TypedDocumentNode<
DeleteSettingMutation,
DeleteSettingMutationVariables
> = gql`
mutation DeleteSettingMutation($id: Int!) {
deleteSetting(id: $id) {
id
}
}
`
interface Props {
setting: NonNullable<FindSettingById['setting']>
}
const Setting = ({ setting }: Props) => {
const [deleteSetting] = useMutation(DELETE_SETTING_MUTATION, {
onCompleted: () => {
toast.success('Setting deleted')
navigate(routes.settings())
},
onError: (error) => {
toast.error(error.message)
},
})
const onDeleteClick = (id: DeleteSettingMutationVariables['id']) => {
if (confirm('Are you sure you want to delete setting ' + id + '?')) {
deleteSetting({ variables: { id } })
}
}
return (
<>
<div className="rw-segment">
<header className="rw-segment-header">
<h2 className="rw-heading rw-heading-secondary">
Setting {setting.id} Detail
</h2>
</header>
<table className="rw-table">
<tbody>
<tr>
<th>Id</th>
<td>{setting.id}</td>
</tr>
<tr>
<th>Enabled</th>
<td>{checkboxInputTag(setting.enabled)}</td>
</tr>
<tr>
<th>Group</th>
<td>{setting.group}</td>
</tr>
<tr>
<th>Name</th>
<td>{setting.name}</td>
</tr>
<tr>
<th>Value</th>
<td>{setting.value}</td>
</tr>
</tbody>
</table>
</div>
<nav className="rw-button-group">
<Link
to={routes.editSetting({ id: setting.id })}
className="rw-button rw-button-blue"
>
Edit
</Link>
<button
type="button"
className="rw-button rw-button-red"
onClick={() => onDeleteClick(setting.id)}
>
Delete
</button>
</nav>
</>
)
}
export default Setting

View File

@ -0,0 +1,40 @@
import type { FindSettingById, FindSettingByIdVariables } from 'types/graphql'
import type {
CellSuccessProps,
CellFailureProps,
TypedDocumentNode,
} from '@redwoodjs/web'
import Setting from 'src/components/Setting/Setting'
export const QUERY: TypedDocumentNode<
FindSettingById,
FindSettingByIdVariables
> = gql`
query FindSettingById($id: Int!) {
setting: setting(id: $id) {
id
enabled
group
name
value
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => <div>Setting not found</div>
export const Failure = ({
error,
}: CellFailureProps<FindSettingByIdVariables>) => (
<div className="rw-cell-error">{error?.message}</div>
)
export const Success = ({
setting,
}: CellSuccessProps<FindSettingById, FindSettingByIdVariables>) => {
return <Setting setting={setting} />
}

View File

@ -0,0 +1,119 @@
import type { EditSettingById, UpdateSettingInput } from 'types/graphql'
import type { RWGqlError } from '@redwoodjs/forms'
import {
Form,
FormError,
FieldError,
Label,
CheckboxField,
TextField,
Submit,
} from '@redwoodjs/forms'
type FormSetting = NonNullable<EditSettingById['setting']>
interface SettingFormProps {
setting?: EditSettingById['setting']
onSave: (data: UpdateSettingInput, id?: FormSetting['id']) => void
error: RWGqlError
loading: boolean
}
const SettingForm = (props: SettingFormProps) => {
const onSubmit = (data: FormSetting) => {
props.onSave(data, props?.setting?.id)
}
return (
<div className="rw-form-wrapper">
<Form<FormSetting> onSubmit={onSubmit} error={props.error}>
<FormError
error={props.error}
wrapperClassName="rw-form-error-wrapper"
titleClassName="rw-form-error-title"
listClassName="rw-form-error-list"
/>
<Label
name="enabled"
className="rw-label"
errorClassName="rw-label rw-label-error"
>
Enabled
</Label>
<CheckboxField
name="enabled"
defaultChecked={props.setting?.enabled}
className="rw-input"
errorClassName="rw-input rw-input-error"
/>
<FieldError name="enabled" className="rw-field-error" />
<Label
name="group"
className="rw-label"
errorClassName="rw-label rw-label-error"
>
Group
</Label>
<TextField
name="group"
defaultValue={props.setting?.group}
className="rw-input"
errorClassName="rw-input rw-input-error"
validation={{ required: true }}
/>
<FieldError name="group" className="rw-field-error" />
<Label
name="name"
className="rw-label"
errorClassName="rw-label rw-label-error"
>
Name
</Label>
<TextField
name="name"
defaultValue={props.setting?.name}
className="rw-input"
errorClassName="rw-input rw-input-error"
validation={{ required: true }}
/>
<FieldError name="name" className="rw-field-error" />
<Label
name="value"
className="rw-label"
errorClassName="rw-label rw-label-error"
>
Value
</Label>
<TextField
name="value"
defaultValue={props.setting?.value}
className="rw-input"
errorClassName="rw-input rw-input-error"
validation={{ required: true }}
/>
<FieldError name="value" className="rw-field-error" />
<div className="rw-button-group">
<Submit disabled={props.loading} className="rw-button rw-button-blue">
Save
</Submit>
</div>
</Form>
</div>
)
}
export default SettingForm

View File

@ -0,0 +1,34 @@
import { gql, useQuery } from '@apollo/client'
const QUERY_VALUE = gql`
query value($name: String, $group: String) {
value(name: $name, group: $group) {
group
name
value
}
}
`
interface SettingValueProps {
name: string
group?: string
}
const SettingValue = ({ name, group = 'default' }: SettingValueProps) => {
const { loading, error, data } = useQuery(QUERY_VALUE, {
variables: { name, group },
})
if (loading) return null
if (error) return 'Error! ' + error
let value = name + ' not found in group ' + group
if (data.value.length >= 1) {
value = data.value[0].value
}
return value
}
export default SettingValue

View File

@ -0,0 +1,102 @@
import type {
DeleteSettingMutation,
DeleteSettingMutationVariables,
FindSettings,
} from 'types/graphql'
import { Link, routes } from '@redwoodjs/router'
import { useMutation } from '@redwoodjs/web'
import type { TypedDocumentNode } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/toast'
import { QUERY } from 'src/components/Setting/SettingsCell'
import { checkboxInputTag, truncate } from 'src/lib/formatters'
const DELETE_SETTING_MUTATION: TypedDocumentNode<
DeleteSettingMutation,
DeleteSettingMutationVariables
> = gql`
mutation DeleteSettingMutation($id: Int!) {
deleteSetting(id: $id) {
id
}
}
`
const SettingsList = ({ settings }: FindSettings) => {
const [deleteSetting] = useMutation(DELETE_SETTING_MUTATION, {
onCompleted: () => {
toast.success('Setting deleted')
},
onError: (error) => {
toast.error(error.message)
},
// This refetches the query on the list page. Read more about other ways to
// update the cache over here:
// https://www.apollographql.com/docs/react/data/mutations/#making-all-other-cache-updates
refetchQueries: [{ query: QUERY }],
awaitRefetchQueries: true,
})
const onDeleteClick = (id: DeleteSettingMutationVariables['id']) => {
if (confirm('Are you sure you want to delete setting ' + id + '?')) {
deleteSetting({ variables: { id } })
}
}
return (
<div className="rw-segment rw-table-wrapper-responsive">
<table className="rw-table">
<thead>
<tr>
<th>Id</th>
<th>Enabled</th>
<th>Group</th>
<th>Name</th>
<th>Value</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{settings.map((setting) => (
<tr key={setting.id}>
<td>{truncate(setting.id)}</td>
<td>{checkboxInputTag(setting.enabled)}</td>
<td>{truncate(setting.group)}</td>
<td>{truncate(setting.name)}</td>
<td>{truncate(setting.value)}</td>
<td>
<nav className="rw-table-actions">
<Link
to={routes.setting({ id: setting.id })}
title={'Show setting ' + setting.id + ' detail'}
className="rw-button rw-button-small"
>
Show
</Link>
<Link
to={routes.editSetting({ id: setting.id })}
title={'Edit setting ' + setting.id}
className="rw-button rw-button-small rw-button-blue"
>
Edit
</Link>
<button
type="button"
title={'Delete setting ' + setting.id}
className="rw-button rw-button-small rw-button-red"
onClick={() => onDeleteClick(setting.id)}
>
Delete
</button>
</nav>
</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
export default SettingsList

View File

@ -0,0 +1,46 @@
import type { FindSettings, FindSettingsVariables } from 'types/graphql'
import { Link, routes } from '@redwoodjs/router'
import type {
CellSuccessProps,
CellFailureProps,
TypedDocumentNode,
} from '@redwoodjs/web'
import Settings from 'src/components/Setting/Settings'
export const QUERY: TypedDocumentNode<FindSettings, FindSettingsVariables> =
gql`
query FindSettings {
settings {
id
enabled
group
name
value
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => {
return (
<div className="rw-text-center">
No settings yet.{' '}
<Link to={routes.newSetting()} className="rw-link">
Create one?
</Link>
</div>
)
}
export const Failure = ({ error }: CellFailureProps<FindSettings>) => (
<div className="rw-cell-error">{error?.message}</div>
)
export const Success = ({
settings,
}: CellSuccessProps<FindSettings, FindSettingsVariables>) => {
return <Settings settings={settings} />
}

View File

@ -0,0 +1,11 @@
import EditSettingCell from 'src/components/Setting/EditSettingCell'
type SettingPageProps = {
id: number
}
const EditSettingPage = ({ id }: SettingPageProps) => {
return <EditSettingCell id={id} />
}
export default EditSettingPage

View File

@ -0,0 +1,7 @@
import NewSetting from 'src/components/Setting/NewSetting'
const NewSettingPage = () => {
return <NewSetting />
}
export default NewSettingPage

View File

@ -0,0 +1,11 @@
import SettingCell from 'src/components/Setting/SettingCell'
type SettingPageProps = {
id: number
}
const SettingPage = ({ id }: SettingPageProps) => {
return <SettingCell id={id} />
}
export default SettingPage

View File

@ -0,0 +1,7 @@
import SettingsCell from 'src/components/Setting/SettingsCell'
const SettingsPage = () => {
return <SettingsCell />
}
export default SettingsPage

2116
yarn.lock

File diff suppressed because it is too large Load Diff