In this section, we need to create a route to render course content. The course content is only rendered if the user is subscribed.
To check if the user is subscribed, we can create a serverless function that accepts the following parameters as below:
In the serverless function, we can write the GROQ query to checks if the user is subscribed. For that, we can write the GROQ query as below:
*[_type == 'subscriptions'
&& customer._ref in *[_type=='customer' && email=='example@gmail.com']._id]{price->{_id, content}}
We can execute the above query in the Sanity Vision to get the reference of the course content below if the subscription of the user exist.
In the above GROQ query, we use email as a reference to fetch the subscription data. If the content id in the serverless function matches the content id of subscription data, then the user is subscribed.
To create the serverless function, we can create the file at the below location:
/src/api/isSubscribe.js
In the isSubscribe.js
, we can create the function as below:
import Axios from "axios"
import validator from "validator"
import jwt from "jsonwebtoken"
import { querySanity } from "../lib/querySanity"
export default async function handler(req, res) {
try {
const token =
req?.body?.token || req?.query?.token || req?.headers["x-access-token"]
const contentRef = req?.body?.contentRef || req?.query?.contentRef
if (!token) {
return res.status(403).send("A token is required for authentication")
}
var decoded = jwt.verify(token, process.env.jwt)
if (!validator.isEmail(decoded.email)) {
throw {
status: 400,
message: "Bad Token",
}
}
let subData = await querySanity(`
*[_type == 'subscriptions' && customer._ref in *[_type=='customer' && email=='${decoded.email}']._id]{price->{_id, content}}
`)
var isExist = false
subData.forEach(sub => {
if (sub?.price?.content?._ref == contentRef) {
isExist = true
}
})
if (isExist) {
res.status(200).json({
is: true,
message: "success",
})
} else {
throw {
is: false,
status: 500,
message: "not subscribe to the course",
}
}
} catch (error) {
const status = error.response?.status || error.statusCode || 500
const message = error.response?.data?.message || error.message
res.status(status).json({
message: error.expose ? message : `Faulty ${req.baseUrl}: ${message}`,
})
}
}
You can download isSubscribe
serverless function code from below:
In the above serverless function, we extract the user details (e.g. email) from the JWT token and execute the GROQ query to fetch the subscription data for the respective user. If the content Id of the request body matches with the content id of the subscription data, we send the success message. View the below image for your reference.
We have now defined the isSubscribe
serverless function logic. Next, we need to create the route in the Gatsby to render the course attachment based on the user's subscription.
To fetch the course content, we can define the GROQ query as below:
*[_type =='content' && slug.current=='${params.product}']{
_id,
title,
exerpt,
'documents': documents[].asset->{
url, originalFilename
},
}
We can execute the above GROQ query in the Sanity Studio as below.
In the above GROQ query, we use slug as a reference to fetch the course attachments. We can use the above GROQ query to fetch course content in the Page component.
To create the Page component, we can create the file at the below location:
src/pages/modules/[product].js
The reason that we enclose the filename in brackets is that we want the slug to be dynamic. Each of the course content can be accessed at a specific slug.
We can access the course content (attachments) at the following slug localhost:8000/modules/[slug-name]
. Based on the slug value, the content data (attachment) is queried and rendered.
In the [product].js
file, we can write the code as below:
import React, { useEffect, useState } from "react"
import { querySanity } from "@lib/querySanity"
import { getCurrentUser, isLoggedIn } from "@utils/auth"
import { FaPaperclip } from "react-icons/fa"
import PortableText from "@components/portabletext/portableText"
import Axios from "axios"
import Layout from "@components/layout"
import Seo from "@components/seo"
import Files from "@components/files"
export default function ModulePrd({ location, params }) {
let [data, setData] = useState(null)
useEffect(() => {
async function fetchData() {
try {
let contentData = await querySanity(`
*[_type =='content' && slug.current=='${params.product}']{
_id,
title,
exerpt,
'documents': documents[].asset->{
url, originalFilename
},
}
`)
let contentRef = contentData[0]._id
let { token } = getCurrentUser()
console.log(`/api/isSubscribe?token=${token}&contentRef=${contentRef}`)
let { data } = await Axios.get(
`/api/isSubscribe?token=${token}&contentRef=${contentRef}`
)
if (data?.is) {
setData(contentData[0])
}
} catch (error) {
console.log(error)
setData(false)
}
}
if (isLoggedIn()) {
fetchData()
}
}, [])
if (data == null) {
return (
<Layout location={location}>
<h3 className="text-base font-normal text-gray-700">
Please wait your data is loading...
</h3>
</Layout>
)
}
return (
<Layout location={location}>
<Seo title={data.title} location={location} description="Modules Page." />
{data == false ? (
<h3 className="text-base font-normal text-gray-700">
You are not authorize to view the content.
</h3>
) : (
<div className="overflow-hidden bg-white shadow sm:rounded-lg">
<div className="px-4 py-5 sm:px-6">
<h3 className="text-lg font-medium leading-6 text-gray-900">
Course
</h3>
<p className="max-w-2xl mt-1 text-sm text-gray-500">
Course details and attachment.
</p>
</div>
<div className="border-t border-gray-200">
<dl>
<div className="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm font-medium text-gray-500">Title</dt>
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{data.title}
</dd>
</div>
<div className="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm font-medium text-gray-500">
Description
</dt>
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{data.exerpt && <PortableText blocks={data.exerpt} />}
</dd>
</div>
<div className="px-4 py-5 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm font-medium text-gray-500">
Attachments
</dt>
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
<ul
role="list"
className="border border-gray-200 divide-y divide-gray-200 rounded-md"
>
{data.documents.map(doc => {
return (
<li className="flex items-center justify-between py-3 pl-3 pr-4 text-sm">
<div className="flex items-center flex-1 w-0">
<FaPaperclip
className="flex-shrink-0 w-5 h-5 text-gray-400"
aria-hidden="true"
/>
<span className="flex-1 w-0 ml-2 truncate">
{doc.originalFilename}
</span>
</div>
<div className="flex-shrink-0 ml-4">
<a
href={doc.url}
className="font-medium text-indigo-600 hover:text-indigo-500"
>
Download
</a>
</div>
</li>
)
})}
</ul>
</dd>
</div>
</dl>
</div>
</div>
)}
</Layout>
)
}
The above code renders in the browser as below.
Let's break down the above code to explain it better.
In the above code, we can access that dynamic slug value in the params key of the props and pass the slug value to the GROQ query.
Next, we can write the code as below to check if the user is subscribed.
In the above code, if the user is subscribed, we can then update the React state.
After we update the React state, you can render the data in JSX as below
We use the Tailwind CSS class name to style the HTML elements. You can view the below Gif to visualize how we use the Tailwind CSS class name to style the HTML elements.
Congrate🎉 We create a course page slug and render the course data/attachment on the page.