This chapter builds the front-end functionality to create, delete, and update store data in Fauna.
Create a new file, pages/store/new.js
, and add the following code.
import Layout from '../../components/Layout';
import StoreForm from '../../components/StoreForm'
export default function NewStorePage() {
return (
<Layout>
<StoreForm />
</Layout>
)
}
Next, create a new form component. Create a new file, components/StoreForm.js
, and add the following code.
import { useState, useEffect } from 'react'
import { useMutation, gql } from '@apollo/client'
import Cookie from 'js-cookie';
const CREATE_NEW_STORE = gql`
mutation CreateNewStore(
$name: String!,
$email: String!,
$categories: [String],
$paymentMethods: [String] $ownerID: ID!) {
createStore(data: {
name: $name,
email: $email,
categories: $categories,
paymentMethods: $paymentMethods
owner: {
connect: $ownerID
}
}) {
_id
name
email
categories
paymentMethods
owner {
_id
email
}
}
}
`;
const INITAL_STATE = {
name: '',
email: '',
paymentMethods: '',
categories: ''
}
export default function StoreForm() {
const [state, setState] = useState(INITAL_STATE)
const [createNewStore, { data, loading, error }] = useMutation(CREATE_NEW_STORE)
useEffect(() => {
if(data) {
alert('New Store Added')
setState(INITAL_STATE)
}
}, [data])
const handleChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value
})
}
const submit = e => {
e.preventDefault()
const cookies = Cookie.get('fauna-session')
console.log('--->', cookies);
createNewStore({
variables: {
...state,
ownerID: JSON.parse(cookies).ownerID,
categories: state.categories.split(','),
paymentMethods: state.paymentMethods.split(',')
}
}).catch(e => console.log(e));
}
return (
<div className="uk-container uk-background-muted" style={{ marginTop: '20px', padding: '20px' }}>
<form onSubmit={submit}>
<div className="uk-margin">
<label >Name</label>
<input
className="uk-input"
type="text"
placeholder="My Store"
name="name"
onChange={handleChange}
value={state.name}
/>
</div>
<div className="uk-margin">
<label >Email</label>
<input
className="uk-input"
type="text"
placeholder="jon@email.com"
name="email"
onChange={handleChange}
value={state.email}
/>
</div>
<div className="uk-margin">
<label >Payment methods (seperated by commas)</label>
<input
className="uk-input"
type="text"
placeholder="Payment methods"
name="paymentMethods"
onChange={handleChange}
value={state.paymentMethods}
/>
</div>
<div className="uk-margin">
<label >Categories (seperated by commas)</label>
<input
className="uk-input"
type="text"
placeholder="Categories"
name="categories"
onChange={handleChange}
value={state.categories}
/>
</div>
<div className="uk-margin">
<input className="uk-input" type="submit" />
</div>
</form>
</div>
);
}
Create a view for editing a store. Create a new file pages/store/[id]/edit.js
. Add the following code snippet to this file.
// pages/store/[id]/edit.js
import Layout from '../../../components/layout';
import StoreEditForm from '../../../components/StoreEditForm'
export default function EditStorePage() {
return (
<Layout>
<StoreEditForm />
</Layout>
)
}
Create a new form component to edit store information. Create a new file, components/StoreEditForm.js
. Add the following code snippet to your file.
// components/StoreEditForm.js
import { useState, useEffect } from 'react'
import { useMutation, useLazyQuery, gql } from '@apollo/client'
import { useRouter } from 'next/router'
import Cookie from 'js-cookie';
const UPDATE_STORE = gql`
mutation updateStore(
$id: ID!
$input: StoreInput!
) {
updateStore(data: $input, id: $id) {
_id
name
email
paymentMethods
categories
owner {
_id
email
}
}
}
`;
const GET_CURRENT_STORE = gql`
query GetCurrentStor($id: ID!) {
findStoreByID(id: $id) {
_id
name
email
categories
paymentMethods
owner {
_id
}
}
}
`;
const INITAL_STATE = {
name: '',
email: '',
paymentMethods: '',
categories: ''
}
export default function StoreEditForm() {
const [state, setState] = useState(INITAL_STATE)
const [updateStore, { data: updatedStore }] = useMutation(UPDATE_STORE)
const [getCurrentStore, { data: currentStore }] = useLazyQuery(GET_CURRENT_STORE)
const router = useRouter()
const { id } = router.query
useEffect(() => {
if(id) {
getCurrentStore({
variables: {
id,
}
})
}
}, [id])
useEffect(() => {
if(currentStore) {
const {paymentMethods, categories} = currentStore.findStoreByID
setState({
...currentStore.findStoreByID,
paymentMethods: paymentMethods.toString(),
categories: categories.toString()
})
}
}, [currentStore])
useEffect(() => {
if(updatedStore) {
alert('Store Updated')
}
}, [updatedStore])
const handleChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value
})
}
const submit = e => {
e.preventDefault()
const cookies = Cookie.get('fauna-session')
if(!cookies) {
router.push('/login');
}
const ownerID = JSON.parse(cookies).ownerID;
if(ownerID !== state.owner._id) {
alert('This store does not belong to you')
return;
}
updateStore({
variables: {
id,
input: {
name: state.name,
email: state.email,
categories: state.categories.split(','),
paymentMethods: state.paymentMethods.split(',')
}
}
}).catch(e => console.log(e));
}
return (
<div className="uk-container uk-background-muted" style={{ marginTop: '20px', padding: '20px' }}>
<form onSubmit={submit}>
<div className="uk-margin">
<label >Name</label>
<input
className="uk-input"
type="text"
placeholder="My Store"
name="name"
onChange={handleChange}
value={state.name}
/>
</div>
<div className="uk-margin">
<label >Email</label>
<input
className="uk-input"
type="text"
placeholder="jon@email.com"
name="email"
onChange={handleChange}
value={state.email}
/>
</div>
<div className="uk-margin">
<label >Payment methods (seperated by commas)</label>
<input
className="uk-input"
type="text"
placeholder="Payment methods"
name="paymentMethods"
onChange={handleChange}
value={state.paymentMethods}
/>
</div>
<div className="uk-margin">
<label >Categories (seperated by commas)</label>
<input
className="uk-input"
type="text"
placeholder="Categories"
name="categories"
onChange={handleChange}
value={state.categories}
/>
</div>
<div className="uk-margin">
<input className="uk-input" type="submit" />
</div>
</form>
</div>
);
}
Fauna allows fine-grained access control. You can set access rules (predicates) so that users can only modify their data and not others.
Head over to the Fauna dashboard. Navigate to Security > Roles > AuthRole. Expand the store collection. Add the following rules to your write access.
Lambda(
["oldData", "newData"],
And(
Equals(Identity(), Select(["data", "owner"], Var("oldData"))),
Equals(
Select(["data", "owner"], Var("oldData")),
Select(["data", "owner"], Var("newData"))
)
)
)
The rule defines that only a store’s owner can update that store.
Similarly, add the following predicate for delete. This rule defines that only a store’s owner can delete a store.
Lambda("ref", Equals(
Identity(), // logged in user
Select(["data", "owner"], Get(Var("ref")))
))
Add a predicate for create as well. The following rule ensures that logged-in users can add store and owner id is associated with a store when it is created.
Lambda("values", Equals(Identity(), Select(["data", "owner"], Var("values"))))
Select Save to save your access control rules. Now, users can only modify or delete their data from your front end.
In this chapter, you learned how to apply fine-grained access control to your data. In the next chapter, you learn more about custom resolvers and Fauna Query Language (FQL).