April 2024 Summaries
22 posts from Strapi
Filter
Month:
Year:
Post Summaries
Back to Blog
This tutorial guides users through deploying their Strapi CMS project to Strapi Cloud, a hassle-free way of hosting Strapi applications. The process involves creating a Strapi Cloud account using GitHub credentials and importing the project via GitHub. Advanced settings may need adjustment depending on the project's structure. Once deployed, users can access their Strapi dashboard and create an admin user. In the next blog post, the front end will be deployed to Vercel and connected with the backend.
Apr 30, 2024
743 words in the original blog post.
This tutorial guides users through deploying a Strapi CMS project using Strapi Cloud. It covers creating a Strapi Cloud account, importing projects via GitHub, and setting up advanced settings for the deployment process. Additionally, it demonstrates how to transfer local Strapi data to seed a new cloud instance. The post also mentions that Strapi Cloud is currently a paid feature with a free 14-day trial period, but an open-source version of Strapi CMS remains available for anyone to use.
Apr 30, 2024
1,562 words in the original blog post.
In this tutorial, learn how to build a customer review and rating app using Strapi CMS backend and solid.js frontend. The process includes setting up Strapi CMS, creating content types, integrating with solid.js for data display, and building the frontend components. This application allows users to submit reviews, including star ratings, names, and written feedback, which are then stored in the Strapi backend.
Apr 29, 2024
2,051 words in the original blog post.
In this tutorial, we learn how to enhance an e-commerce platform by adding advanced search capabilities using Strapi, Next.Js, and Algolia Search. We begin by setting up a new Strapi application and creating a Product collection with fields such as name, image, price, and description. Then, we integrate Algolia with our Strapi backend to enable the search functionality. After that, we set up the frontend using Next.Js and install the necessary packages for Algolia integration. Finally, we build the Search interface in our Next.Js frontend to show the products and perform search operations. We also extend the search features by adding facets, filtering, and sorting capabilities.
Apr 26, 2024
1,678 words in the original blog post.
In this tutorial, we will create a personal goals tracking application using Flutter, Riverpod, GraphQL, and Strapi CMS. The app will allow users to add, start, and edit their goals or targets. We will use the following technologies and tools:
1. Flutter: A free and open-source UI software development kit created by Google. It is used to develop applications for Android, iOS, Linux, macOS, Windows, and the web from a single codebase.
2. Riverpod: A state management library for Flutter that helps manage application states efficiently.
3. GraphQL: A query language for APIs that enables declarative data fetching and allows clients to define the structure of the required data.
4. Strapi: An open-source Node.js CMS built with JavaScript, which provides a powerful and customizable API for managing content and data.
By following this tutorial, you will learn how to integrate these technologies together to create a feature-rich personal goals tracking application.
To get started, ensure that you have the following prerequisites:
1. Flutter SDK installed on your development machine.
2. An understanding of Dart programming language and familiarity with Flutter framework.
3. Basic knowledge of GraphQL and RESTful APIs.
4. A working installation of Node.js, npm, and Yarn package managers.
5. Familiarity with Strapi CMS or a similar headless CMS platform.
Once you have the prerequisites in place, follow these steps to create your personal goals tracking application:
Step 1: Set up the Flutter project
Create a new Flutter project by running the following command in your terminal or command prompt:
```bash
flutter create personal_goals_app
cd personal_goals_app
```
Step 2: Install Riverpod package
Add the Riverpod package to your Flutter project by updating the `pubspec.yaml` file with the following dependency:
```yaml
dependencies:
flutter:
sdk: flutter
riverpod: ^1.0.0
```
Run the following command in your terminal or command prompt to fetch and install the Riverpod package:
```bash
flutter pub get
```
Step 3: Set up GraphQL client
Create a new file named `graphql_client.dart` inside the `lib/src/graphql/` directory of your Flutter project. Define the GraphQL client with the Strapi GraphQL URL as follows:
```dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
ValueNotifier<GraphQLClient> initializeClient(String graphqlEndpoint) {
final HttpLink httpLink = HttpLink(graphqlEndpoint);
return ValueNotifier(
GraphQLClient(
link: httpLink,
cache: GraphQLCache(store: InMemoryStore()),
),
);
}
const String strapiGraphQLURL = 'http://localhost:1337/graphql';
final graphqlClient = initializeClient(strapiGraphQLURL);
```
Step 4: Define GraphQL mutations and queries
Create two new files named `mutations.dart` and `queries.dart` inside the `lib/src/graphql/` directory of your Flutter project. In these files, define Strapi GraphQL mutations for creating, updating, and deleting goals, as well as a GraphQL query for fetching all goals from the Strapi database.
Step 5: Update Riverpod provider to use real queries and mutations
Update the `goal_provider.dart` file inside the `lib/src/provider/` directory of your Flutter project to use the real queries and mutations defined in Step 4. This provider is responsible for managing the state of goals in your app and facilitating communication with the Strapi backend through GraphQL mutations and queries.
Step 6: Enhance Goal model
Update the `goal_model.dart` file inside the `lib/src/goals/models/` directory of your Flutter project to accommodate the requirements for integrating with a Strapi backend. These enhancements include adding methods such as `fromJson`, `calculateStatus`, and converting string representations of enums into their corresponding enum values.
Step 7: Display goals fetched from Strapi in home page
Update the `home.dart` file inside the `lib/src/pages/` directory of your Flutter project to fetch the goals from Strapi and display them in a list view. Ensure that you use the `ConsumerWidget` provided by Riverpod for efficient state management.
Step 8: Test the personal goals tracking application
Run your Flutter app on an emulator or physical device by executing the following command in your terminal or command prompt:
```bash
flutter run
```
By the end of this tutorial, you should have a working personal goals tracking application that allows a user add, start and edit a goal or target.
Strapi API provides a powerful and customizable API for managing content and data. With Strapi, we can define custom content types, set permissions, and expose APIs tailored to our application's needs. Personally, It is very easy to use and quick to learn. Benefits of Using Riverpod, Flutter, GraphQL, and Strapi Together:
Computer Engineering graduate, proficient in building mobile applications with Flutter and web applications with Vue.js. Beyond coding, I enrich my time playing an instrument, learning new languages, exploring diverse cultures, and developing my own mobile app. As a tech writer, I'm dedicated to sharing knowledge and insights. I am always eager to blend technical skills with creative pursuits.```
Apr 25, 2024
4,997 words in the original blog post.
In this text, the author explains how to implement search and pagination functionalities using Next.js and Strapi CMS. They provide detailed code examples for creating a Search component that updates the URL with each keystroke, as well as a PaginationComponent that allows users to navigate through different pages of content. The author also discusses how to integrate these components into a larger project and provides tips on troubleshooting common issues.
Apr 23, 2024
2,124 words in the original blog post.
In this tutorial, we will learn how to implement search and pagination functionalities using Next.js with Strapi CMS. We will create a Search component that updates the URL on each keystroke and a PaginationComponent that allows users to navigate through different pages of content. The project has been updated to use Next.js 15 and Strapi 5, and we will also cover how to deploy our project to Strapi Cloud and Vercel.
Apr 23, 2024
2,287 words in the original blog post.
This post discusses two popular methods for handling authorization and authentication in web development: JSON Web Tokens (JWT) and cookie storage. It provides definitions, structures, advantages, and disadvantages of both methods and compares them to help developers decide which is best for their project. JWTs are compact, self-contained tokens that can be used across different domains, while cookies are stored on the client's browser and are primarily used for tracking user sessions. Both methods have pros and cons, with JWTs offering more security but requiring additional implementation effort, while cookies are simpler to implement but less secure. The choice between the two depends on the specific needs of the project and the level of security required.
Apr 22, 2024
1,720 words in the original blog post.
In this tutorial, you will learn how to create an AI-driven FAQ system using Strapi, LangChain.js, and OpenAI. The system allows users to pose queries related to Strapi CMS and receive accurate responses generated by a GPT model. To follow along with the tutorial, you need to have Node.js installed on your system, as well as create an account with OpenAI to obtain an API key.
The AI-powered FAQ system is built in two parts: a backend server using Express.js and a frontend React app for user interaction. The backend server uses the RAG (Retriever Augmented Generation) approach, which combines information retrieval with large language models (LLMs) to provide more factually grounded answers.
The core of your FAQ system will reside in an Express.js server. It will leverage the RAG approach by managing incoming requests, retrieving FAQ data from Strapi, processing user queries, and utilizing RAG for generating AI-driven responses. The frontend React app provides a user interface for interacting with the AI-powered FAQ system hosted on the server.
After setting up your project and installing the required dependencies, you will configure the data source (Strapi), obtain an OpenAI API key, initialize a React project, and finally develop the AI-driven FAQ system using Strapi, LangChain.js, and OpenAI. The final result is an AI & Strapi-powered FAQ system that integrates seamlessly with Strapi for managing your FAQ data through a centralized platform.
Apr 19, 2024
3,163 words in the original blog post.
In this tutorial, we covered how to create a full-stack application using Next.js 14 as our frontend framework and Strapi as our backend API. We focused on implementing CRUD operations with user authentication and permission handling.
Here's an overview of the topics we covered:
1. Setting up the development environment for both Next.js 14 and Strapi.
2. Creating a new content type in Strapi called "Summary" to store summaries data.
3. Implementing user authentication using JWT tokens with Strapi's built-in users-permissions plugin.
4. Developing the frontend application using Next.js 14, including routing and fetching data from the backend API.
5. Creating custom middleware in Strapi to handle CRUD operations for both "Summary" content type and user authentication.
6. Implementing permission handling by verifying user permissions before allowing data manipulation through middleware functions.
7. Testing our application using Insomnia, a powerful HTTP client that allows us to send requests directly to the backend API.
By following this tutorial, you should now have a solid understanding of how to build a full-stack application with Next.js 14 and Strapi while implementing CRUD operations and user authentication with permission handling.
In future posts, we will continue exploring more advanced topics related to building applications using these technologies, such as pagination, search functionality, and integrating third-party services like Stripe for payment processing.
Thank you for reading this tutorial, and I hope it has been helpful in your journey towards mastering Next.js 14 and Strapi!
Apr 16, 2024
4,595 words in the original blog post.
In this tutorial, we focused on making our app more secure and user-friendly by setting up CRUD operations that are specific to each user. This way, each person can only update or delete their own summaries, adding a strong layer of protection and control. With custom middleware handling these checks, we made sure that users see only their own content and that any attempts to view or change someone else’s data are blocked.
Here's a quick summary of everything we covered:
1. We reviewed the basics of CRUD operations (Create, Read, Update, Delete) and how they map to specific HTTP methods and routes in Strapi.
2. We discussed using JSON Web Tokens (JWT) for authentication and ensuring that each request is legitimate.
3. We learned about route middleware in Strapi, which acts as a security checkpoint for each request, allowing us to add additional checks such as checking permissions.
4. We implemented our form logic first, then added the middleware to handle the authorization check.
5. We tested out our frontend and fixed an issue with showing summaries from the user who is logged in by creating a new middleware.
6. We restarted our Strapi backend and saw that each person can only update or delete their own summaries, adding a strong layer of protection and control.
This setup combines Strapi's middleware with Next.js to create a simple and secure app that works well even as more users join. Now, each user has a clear view of their own data, and we're keeping everything safe by preventing unauthorized access. In the next part, we'll keep building out new features to make this app even better. Thanks for following along, and happy coding!
Apr 16, 2024
3,296 words in the original blog post.
This blog post explores how Strapi, a headless CMS, empowers non-technical teams to build robust business solutions through its API-driven strategy and built-in security. The author delves into the benefits of no-code/low-code solutions in light of AI advancements and demonstrates how Strapi can be integrated with popular tools like Typeform and Make.com without coding experience. The post provides a step-by-step guide to unlocking Strapi's potential, including user registration, integration with external services, and creating a Typeform form connected to a pre-made Make.com scenario. By leveraging no-code/low-code tools like Strapi, businesses can accelerate innovation and democratize development.
Apr 15, 2024
1,425 words in the original blog post.
In this tutorial, we learn how to use Strapi, ChatGPT, and Next.js to build an app that displays recipes using AI. The project involves creating a Strapi API for structured storage and management of recipe data, integrating AI with the help of ChatGPT to provide additional insights or suggestions, and using Next.js as the frontend framework. We also learn how to make our API publicly accessible and connect it to OpenAI. Finally, we create reusable components in Next.Js for displaying the recipes and use a custom hook to fetch data from Strapi.
Apr 11, 2024
3,770 words in the original blog post.
In the final part of this tutorial, we will create a summary details page and discuss updating and deleting summaries. We will also add policies to ensure that users can only modify their content.
First, let's start by creating our server action for updating our frontend.
We Will Start By Creating A Server Action To Update SummaryForm.tsx File And Paste The Following Code:
1
2345678910111213141516171819201111111111111111111111111111111
Apr 10, 2024
7,226 words in the original blog post.
Strapi has introduced a new feature called Scheduling for Releases, which allows users to schedule content updates in advance. This update aims to streamline the content management process by reducing manual errors and allowing more time for creating impactful content. Additionally, Strapi has added several enhancements and plugins to improve user experience and functionality. These include improvements to GraphQL queries, data transfer and uploads, content management fixes, content releases stability, upload and I18N plugin fixes, admin interface fixes, and new marketplace plugins such as Algolia, Prev-Next Buttons, AI Image Generation Plugin, and more.
Apr 10, 2024
1,077 words in the original blog post.
In this tutorial, we will continue building our Next.js app by adding a summary details page and handling update and delete operations for summaries. We will also discuss how to add policies in Strapi to ensure that users can only modify their content.
First, let's create the SummaryDetails component inside the components/custom folder. This component will display the summary title, video URL, and summary text. It will also include a form for updating or deleting the summary.
```javascript
import { useFormStatus } from "react-dom";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { TrashIcon, PencilIcon } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle, Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/card";
import ReactMarkdown from "react-markdown";
interface SummaryDetailsProps {
item: any;
}
export function SummaryDetails({ item }: Readonly<SummaryDetailsProps>) {
const status = useFormStatus();
return (
<Card className={cn("mb-8 relative h-auto")}>
<CardHeader>
<CardTitle>{item.title}</CardTitle>
</CardHeader>
<CardContent>
<div>
<form>
<Input id="title" name="title" placeholder="Update your title" required className="mb-4" defaultValue={item.title} />
<Textarea
name="summary"
className="markdown-preview relative w-full h-[600px] overflow-auto scroll-smooth p-4 px-3 py-2 text-sm bg-white dark:bg-gray-800 bg-transparent border border-gray-300 dark:border-gray-700 rounded-md shadow-sm mb-4 placeholder:text-muted-foreground focus-visible:outline-none focus-visible:bg-gray-50 focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
defaultValue={item.summary}
/>
</form>
<input type="hidden" name="id" value={item.documentId} />
<SubmitButton text="Update Summary" loadingText="Updating Summary" />
</div>
<form>
<DeleteButton className="absolute right-4 top-4 bg-red-700 hover:bg-red-600">
<TrashIcon className="w-4 h-4" />
</DeleteButton>
<UpdateButton className="absolute right-12 top-4 bg-blue-700 hover:bg-blue-600">
<PencilIcon className="w-4 h-4" />
</UpdateButton>
</form>
</CardContent>
</Card>
);
}
```
Next, let's create the UpdateSummaryForm component inside the components/custom folder. This component will handle updating summary data and display a success message after the update is successful.
```javascript
import { useFormStatus } from "react-dom";
import { cn } from "@/lib/utils";
import { Input, Textarea } from "@/components/ui/input";
import { Card, CardContent, CardFooter, CardHeader, CardTitle, Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/card";
import ReactMarkdown from "react-markdown";
import { SubmitButton } from "@/components/custom/submit-button";
import { DeleteButton } from "@/components/custom/delete-button";
interface UpdateSummaryFormProps {
item: any;
}
export function UpdateSummaryForm({ item }: Readonly<UpdateSummaryFormProps>) {
const status = useFormStatus();
return (
<Card className={cn("mb-8 relative h-auto")}>
<CardHeader>
<CardTitle>Video Summary</CardTitle>
</CardHeader>
<CardContent>
<div>
<form>
<Input id="title" name="title" placeholder="Update your title" required className="mb-4" defaultValue={item.title} />
<Textarea
name="summary"
className="markdown-preview relative w-full h-[600px] overflow-auto scroll-smooth p-4 px-3 py-2 text-sm bg-white dark:bg-gray-800 bg-transparent border border-gray-300 dark:border-gray-700 rounded-md shadow-sm mb-4 placeholder:text-muted-foreground focus-visible:outline-none focus-visible:bg-gray-50 focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
defaultValue={item.summary}
/>
</form>
<input type="hidden" name="id" value={item.documentId} />
<SubmitButton text="Update Summary" loadingText="Updating Summary" />
</div>
</CardContent>
</Card>
);
}
```
Now, let's update our page.tsx file with the following code. This component will display the summary details and include a form for updating or deleting the summary.
```javascript
import { getSummaryById } from "@/data/loaders";
import { SummaryDetails } from "@/components/custom/summary-details";
import { UpdateSummaryForm } from "@/components/custom/update-summary-form";
import { DeleteButton } from "@/components/custom/delete-button";
interface ParamsProps {
params: {
videoId: string;
};
}
export default async function SummaryPageRoute(props: Readonly<ParamsProps>) {
const params = await props?.params;
const { videoId } = params;
const data = await getSummaryById(videoId);
return (
<div className="flex flex-col">
<h1>Summary Page</h1>
<hr />
<SummaryDetails item={data.data} />
<UpdateSummaryForm item={data.data} />
<DeleteButton className="absolute right-4 top-4 bg-red-700 hover:bg-red-600">
<TrashIcon className="w-4 h-4" />
</DeleteButton>
</div>
);
}
```
In this tutorial, we have created a summary details page and added forms for updating or deleting summaries. We also discussed how to add policies in Strapi to ensure that users can only modify their content. In the next post, we will explore adding authentication and user profiles to our Next.js app.
Apr 10, 2024
7,730 words in the original blog post.
The article discusses the importance of web accessibility and how it can be improved using Strapi CMS. Accessibility is defined as designing products or services to be usable by people with disabilities, ensuring that everyone can benefit from digital content regardless of their abilities or circumstances. Web Content Accessibility Guidelines (WCAG) are established by the World Wide Web Consortium (W3C) and cover a wide range of criteria for making web content more accessible.
Strapi CMS allows developers to easily integrate accessibility into their development process, with plugins like the All-in-One Accessibility Plugin developed by Skynet Technologies. This plugin improves websites' compliance with WCAG and ADA standards and offers features such as high contrast, content scaling, accessibility profiles, and highlight links.
Practical ways to improve website accessibility include using semantic HTML, providing alternative text for images, using high-contrast colors, and captioning multimedia content. The article encourages learning more about accessibility standards and exploring additional Strapi plugins from the marketplace.
Apr 08, 2024
1,238 words in the original blog post.
This tutorial guides users through building a chat application using React, Strapi, and Firebase. The app combines React's frontend strength with Strapi backend data management skills, Firebase's authentication and messaging functionality. Users are taught how to create a Strapi chat app, integrate Firebase for user authentication, and get real-time updates. Key steps include setting up the Strapi project, installing necessary dependencies, creating various components like navigation bar, login page, send message form, and integrating with Firebase for user authentication. The final product is a functional chat application that allows users to exchange messages in real-time.
Apr 05, 2024
3,424 words in the original blog post.
In this tutorial, we will continue building our YouTube video summarizer application using Next.js and Strapi. We have already built the frontend for updating user profile information. Now, let's move on to the main feature of our app, which is generating a summary of YouTube videos.
First, let's create a new page in our Next.js application that will be responsible for displaying the video summarizer form and results. Navigate to `pages/dashboard` and create a new file called `VideoSummaryPage.tsx`. Paste in the following code:
```javascript
import { useRouter } from "next/navigation";
import Image from "next/image";
import { useState, useEffect } from "react";
import VideoPlayer from "@/components/custom/VideoPlayer";
import TextareaAutosize from "react-textarea-autosize";
import Button from "@/components/ui/button";
import { uploadSummaryAction } from "@/data/actions/profile-actions";
import { getUserMeLoader } from "@/data/services/get-user-me-loader";
interface VideoSummaryPageProps {}
export default function VideoSummaryPage(props: Readonly<VideoSummaryPageProps>) {
const router = useRouter();
const [videoId, setVideoId] = useState("");
const [summary, setSummary] = useState("");
const [loading, setLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!videoId) return;
setLoading(true);
try {
const user = await getUserMeLoader();
if (!user) throw new Error("User not found");
const responseData = await uploadSummaryAction(user.data.id, videoId, summary);
if (responseData.error) {
setErrorMessage(responseData.error);
return;
}
router.push("/dashboard/summary-results?videoId=" + videoId);
} catch (error: any) {
console.log("error", error);
setErrorMessage(error?.message || "Something went wrong, please try again.");
} finally {
setLoading(false);
}
};
return (
<div className="flex flex-col space-y-4">
<h1 className="text-2xl font-bold">Video Summary Generator</h1>
<form onSubmit={handleSubmit}>
<label htmlFor="videoId" className="block text-sm font-medium leading-6 text-gray-900">
Video ID
</label>
<div className="mt-2">
<input
type="text"
name="videoId"
id="videoId"
value={videoId}
onChange={(e) => setVideoId(e.target.value)}
placeholder="Enter the video ID (example: dQw4w9WgXc)"
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
<label htmlFor="summary" className="block text-sm font-medium leading-6 text-gray-900">
Summary Text
</label>
<div className="mt-2">
<TextareaAutosize
id="summary"
name="summary"
value={summary}
onChange={(e) => setSummary(e.target.value)}
placeholder="Enter the summary text here..."
minRows={3}
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
<Button loading={loading} type="submit">
Generate Summary
</Button>
</form>
{errorMessage && (
<p className="text-red-500 text-xs">{errorMessage}</p>
)}
</div>
);
}
```
In this component, we have a form that takes in the video ID and summary text. When the user submits the form, it triggers the `handleSubmit` function, which calls our server action to upload the summary data to Strapi. If the upload is successful, it redirects the user to the summary results page.
Now let's create our server action for uploading the summary data. Navigate to `src/data/actions/profile-actions.ts` and add the following code:
```javascript
import { getAuthToken } from "./get-token";
import { mutateData } from "./mutate-data";
import { flattenAttributes } from "@/lib/utils";
export async function uploadSummaryAction(userId: string, videoId: string, summary: string) {
const authToken = await getAuthToken();
if (!authToken) throw new Error("No auth token found");
try {
const responseData = await mutateData(
"POST",
`/api/users/${userId}/video-summaries?videoId=${videoId}`,
{ summary }
);
if (!responseData) {
throw new Error("Something went wrong, please try again.");
}
const flattenedData = flattenAttributes(responseData);
return flattenedData;
} catch (error: any) {
console.log("error", error);
throw error;
}
}
```
In this server action, we are using the `mutateData` function to send a POST request to our Strapi API endpoint for uploading video summary data. The endpoint URL includes the user ID and video ID as query parameters. We also pass in the summary text as payload data.
Now let's create our corresponding route in Strapi for handling this server action. Navigate to `src/data/services` and create a new file called `profile-actions.ts`. Paste in the following code:
```javascript
import { getAuthToken } from "./get-token";
import { mutateData } from "./mutate-data";
import { flattenAttributes } from "@/lib/utils";
export async function uploadSummaryAction(userId: string, videoId: string, summary: string) {
const authToken = await getAuthToken();
if (!authToken) throw new Error("No auth token found");
try {
const responseData = await mutateData(
"POST",
`/api/users/${userId}/video-summaries?videoId=${videoId}`,
{ summary }
);
if (!responseData) {
throw new Error("Something went wrong, please try again.");
}
const flattenedData = flattenAttributes(responseData);
return flattenedData;
} catch (error: any) {
console.log("error", error);
throw error;
}
}
```
In this server action, we are using the `mutateData` function to send a POST request to our Strapi API endpoint for uploading video summary data. The endpoint URL includes the user ID and video ID as query parameters. We also pass in the summary text as payload data.
Now let's create our corresponding route in Strapi for handling this server action. Navigate to `src/data/services` and create a new file called `profile-actions.ts`. Paste in the following code:
```javascript
import { getAuthToken } from "./get-token";
import { mutateData } from "./mutate-data";
import { flattenAttributes } from "@/lib/utils";
export async function uploadSummaryAction(userId: string, videoId: string, summary: string) {
const authToken = await getAuthToken();
if (!authToken) throw new Error("No auth token found");
try {
const responseData = await mutateData(
"POST",
`/api/users/${userId}/video-summaries?videoId=${videoId}`,
{ summary }
);
if (!responseData) {
throw new Error("Something went wrong, please try again.");
}
const flattenedData = flattenAttributes(responseData);
return flattenedData;
} catch (error: any) {
console.log("error", error);
throw error;
}
}
```
In this server action, we are using the `mutateData` function to send a POST request to our Strapi API endpoint for uploading video summary data. The endpoint URL includes the user ID and video ID as query parameters. We also pass in the summary text as payload data.
Now let's create our corresponding route in Strapi for handling this server action. Navigate to `src/data/services` and create a new file called `profile-actions.ts`. Paste in the following code:
```javascript
import { getAuthToken } from "./get-token";
import { mutateData } from "./mutate-data";
import { flattenAttributes } from "@/lib/utils";
export async function uploadSummaryAction(userId: string, videoId: string, summary: string) {
const authToken = await getAuthToken();
if (!authToken) throw new Error("No auth token found");
try {
const responseData = await mutateData(
"POST",
`/api/users/${userId}/video-summaries?videoId=${videoId}`,
{ summary }
);
if (!responseData) {
throw new Error("Something went wrong, please try again.");
}
const flattenedData = flattenAttributes(responseData);
return flattenedData;
} catch (error: any) {
console.log("error", error);
throw error;
}
}
```
In this server action, we are using the `mutateData` function to send a POST request to our Strapi API endpoint for uploading video summary data. The endpoint URL includes the user ID and video ID as query parameters. We also pass in the summary text as payload data.
Now let's create our corresponding route in Strapi for handling this server action. Navigate to `src/data/services` and create a new file called `profile-actions.ts`. Paste in the following code:
```javascript
import { getAuthToken } from "./get-token";
import { mutateData } from "./mutate-data";
import { flattenAttributes } from "@/lib/utils";
export async function uploadSummaryAction(userId: string, videoId: string, summary: string) {
const authToken = await getAuthToken();
if (!authToken) throw new Error("No auth token found");
try {
const responseData = await mutateData(
"POST",
`/api/users/${userId}/video-summaries?videoId=${videoId}`,
{ summary }
);
if (!responseData) {
throw new Error("Something went wrong, please try again.");
}
const flattenedData = flattenAttributes(responseData);
return flattenedData;
} catch (error: any) {
console.log("error", error);
throw error;
}
}
```
In this server action, we are using the `mutateData` function to send a POST request to our Strapi API endpoint for uploading video summary data. The endpoint URL includes the user ID and video ID as query parameters. We also pass in the summary text as payload data.
Now let's move on generating summaries of our YouTube videos.
Apr 03, 2024
5,420 words in the original blog post.
In this tutorial, we will learn how to create a Dashboard layout with an Account section where the user can update their first name, last name, bio, and image using Next.js Server Actions. We will also cover file uploads in Next.js.
First, let's create our profile-actions.ts file inside the actions folder under data/actions directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"use server";
import { z } from "zod";
import qs from "qs";
import { getUserMeLoader } from "@/data/services/get-user-me-loader";
import { mutateData } from "@/data/services/mutate-data";
import { flattenAttributes } from "@/lib/utils";
export async function updateProfileAction(
userId: string,
prevState: any,
formData: FormData
) {
const rawFormData = Object.fromEntries(formData);
const query = qs.stringify({
populate: "*",
});
const payload = {
firstName: rawFormData.firstName,
lastName: rawFormData.lastName,
bio: rawFormData.bio,
};
const responseData = await mutateData(
"PUT",
`/api/users/${userId}?${query}`,
payload
);
if (!responseData) {
return {
...prevState,
strapiErrors: null,
message: "Ops! Something went wrong. Please try again.",
};
}
if (responseData.error) {
return {
...prevState,
strapiErrors: responseData.error,
message: "Failed to Register.",
};
}
const flattenedData = flattenAttributes(responseData);
return {
...prevState,
message: "Profile Updated",
data: flattenedData,
strapiErrors: null,
};
}
In this file, we are importing the necessary dependencies and creating a server action called updateProfileAction. This action takes three parameters: userId, prevState, and formData. The first parameter is the user's ID, which we will use to identify the user in our Strapi API. The second parameter is the previous state of the form, which we will use to update the form data after a successful update. The third parameter is the form data that the user submitted.
Next, let's create our profile-form.tsx file inside the forms folder under components/forms directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"use client";
import React from "react";
import { useFormState } from "react-dom";
import { cn } from "@/lib/utils";
import { uploadProfileImageAction } from "@/data/actions/profile-actions";
import { SubmitButton } from "@/components/custom/SubmitButton";
import ImagePicker from "@/components/custom/ImagePicker";
import { ZodErrors } from "@/components/custom/ZodErrors";
import { StrapiErrors } from "@/components/custom/StrapiErrors";
interface ProfileImageFormProps {
id: string;
url: string;
alternativeText: string;
}
const initialState = {
message: null,
data: null,
strapiErrors: null,
zodErrors: null,
};
export function ProfileImageForm({
data,
className,
}: {
data: Readonly<ProfileImageFormProps>,
className?: string,
}) {
const uploadProfileImageWithIdAction = uploadProfileImageAction.bind(
null,
data?.id
);
const [formState, formAction] = useFormState(
uploadProfileImageWithIdAction,
initialState
);
return (
<form className={cn("space-y-4", className)} action={formAction}>
<div className="">
<ImagePicker
id="image"
name="image"
label="Profile Image"
defaultValue={data?.url || ""}
/>
<ZodErrors error={formState.zodErrors?.image} />
<StrapiErrors error={formState.strapiErrors} />
</div>
<div className="flex justify-end">
<SubmitButton text="Update Image" loadingText="Saving Image" />
</div>
</form>
);
}
In this file, we are importing the necessary dependencies and creating a client component called ProfileImageForm. This component takes two parameters: data and className. The first parameter is an object containing the user's profile image information, which includes the ID, URL, and alternative text of the image. The second parameter is an optional string that specifies additional CSS classes to apply to the form.
Next, let's create our get-user-me-loader.ts file inside the services folder under data/services directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { getAuthToken } from "./get-token";
import { mutateData } from "./mutate-data";
import { flattenAttributes } from "@/lib/utils";
import { getStrapiURL } from "@/lib/utils";
export async function getUserMeLoader() {
const authToken = await getAuthToken();
if (!authToken) throw new Error("No auth token found");
const baseUrl = getStrapiURL();
const url = new URL("/api/users/me", baseUrl);
try {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${authToken}` },
});
const dataResponse = await response.json();
return flattenAttributes(dataResponse);
} catch (error) {
console.error("Error getting user me:", error);
throw error;
}
}
In this file, we are importing the necessary dependencies and creating a server action called getUserMeLoader. This action takes no parameters and returns an object containing the user's profile information.
Next, let's create our mutate-data.ts file inside the services folder under data/services directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { getAuthToken } from "./get-token";
import { getStrapiURL } from "@/lib/utils";
export async function mutateData(method: string, path: string, payload?: any) {
const baseUrl = getStrapiURL();
const authToken = await getAuthToken();
ififififififififififififififififififififififififififififififif
Apr 03, 2024
5,420 words in the original blog post.
In this tutorial, we will create a Dashboard layout with an Account section where users can update their first name, last name, bio, and image using Next.js and Strapi. We will also handle file uploads using NextJs server actions.
First, let's set up our project by installing the necessary dependencies:
```bash
npx create-next-app --ts
cd your-project-name
npm install axios js-cookie strapi-sdk
```
Next, we will create a new file named `_document.js` in the pages directory and paste the following code to customize our Next.js application:
```javascript
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
```
Now, let's create a new file named `_app.js` in the pages directory and paste the following code to set up our custom auth check:
```javascript
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import Cookies from 'js-cookie';
import axios from 'axios';
function MyApp({ Component, pageProps }) {
const router = useRouter();
const [loading, setLoading] = useState(true);
useEffect(() => {
const handleStart = () => setLoading(true);
const handleComplete = () => setLoading(false);
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);
return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleComplete);
};
}, [router]);
useEffect(() => {
const jwt = Cookies.get('jwt');
if (!loading && !jwt) {
router.push('/login');
}
}, [loading, router]);
return loading ? (
<div>Loading...</div>
) : (
<Component {...pageProps} />
);
}
export default MyApp;
```
Now that we have our project set up let's create a new file named `login.js` in the pages directory and paste the following code to handle user login:
```javascript
import React, { useState } from 'react';
import axios from 'axios';
import Router from 'next/router';
import Cookies from 'js-cookie';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post('/api/auth', { email, password });
Cookies.set('jwt', response.data.token);
Router.push('/dashboard');
} catch (error) {
console.error(error);
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
<button type="submit">Login</button>
</form>
</div>
);
};
export default Login;
```
Next, let's create a new file named `dashboard.js` in the pages directory and paste the following code to handle user dashboard:
```javascript
import React from 'react';
import { useState } from 'react';
import axios from 'axios';
import Router from 'next/router';
import Cookies from 'js-cookie';
const Dashboard = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const jwt = Cookies.get('jwt');
if (!jwt) {
Router.push('/login');
} else {
axios.get('/api/users/me', { headers: { Authorization: `Bearer ${jwt}` } })
.then((response) => setUser(response.data))
.catch((error) => console.error(error));
}
}, []);
return (
<div>
{user ? (
<div>
<h1>{user.firstName} {user.lastName}</h1>
<p>{user.bio}</p>
<img src={user.image.url} alt={user.image.alternativeText} />
</div>
) : (
<div>Loading...</div>
)}
</div>
);
};
export default Dashboard;
```
Now, let's create a new file named `auth.js` in the pages directory and paste the following code to handle user authentication:
```javascript
import axios from 'axios';
const auth = async (req) => {
try {
const response = await axios.post('/api/auth', req.body);
return { success: true, data: response.data };
} catch (error) {
console.error(error);
return { success: false, error: 'Invalid email or password.' };
}
};
export default auth;
```
Next, let's create a new file named `users.js` in the pages directory and paste the following code to handle user data:
```javascript
import axios from 'axios';
const users = async (req) => {
try {
const response = await axios.get('/api/users/me', { headers: { Authorization: `Bearer ${req.headers.authorization}` } });
return { success: true, data: response.data };
} catch (error) {
console.error(error);
return { success: false, error: 'Failed to fetch user data.' };
}
};
export default users;
```
Now that we have our backend set up let's create a new file named `get-user-me-loader.ts` in the services directory and paste the following code to handle user data loading:
```typescript
import qs from 'qs';
import { getAuthToken } from './get-token';
import { getStrapiURL } from '../lib/utils';
export async function getUserMeLoader() {
const baseUrl = getStrapiURL();
const url = new URL('/api/users/me', baseUrl);
url.search = qs.stringify({
populate: 'image',
});
const authToken = await getAuthToken();
if (!authToken) return { ok: false, data: null, error: null };
try {
const response = await fetch(url.href, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`,
},
});
const data = await response.json();
if (data.error) return { ok: false, data: null, error: data.error };
return { ok: true, data: data, error: null };
} catch (error) {
console.log(error);
return { ok: false, data: null, error: error };
}
}
```
Now that we have our backend set up let's create a new file named `update-profile-action.ts` in the actions directory and paste the following code to handle user profile updates:
```typescript
import { z } from 'zod';
import qs from 'qs';
import { revalidatePath } from 'next/cache';
import { getUserMeLoader } from '../services/get-user-me-loader';
import { mutateData } from '../services/mutate-data';
const profileSchema = z.object({
firstName: z.string().min(1, 'First name is required.'),
lastName: z.string().min(1, 'Last name is required.'),
bio: z.string(),
});
export async function updateProfileAction(
userId: string,
prevState: any,
formData: FormData
) {
const rawFormData = Object.fromEntries(formData);
const payload = {
firstName: rawFormData.firstName,
lastName: rawFormData.lastName,
bio: rawFormData.bio,
};
const responseData = await mutateData('PUT', `/api/users/${userId}`, payload);
revalidatePath('/dashboard');
return {
...prevState,
data: responseData,
message: 'Profile updated.',
};
}
```
Now that we have our backend set up let's create a new file named `update-image-action.ts` in the actions directory and paste the following code to handle user profile image updates:
```typescript
import { z } from 'zod';
import qs from 'qs';
import { revalidatePath } from 'next/cache';
import { getUserMeLoader } from '../services/get-user-me-loader';
import { mutateData } from '../services/mutate-data';
import { fileDeleteService, fileUploadService } from '../services/file-service';
const imageSchema = z.object({
image: z
.any()
.refine((file) => {
if (file.size === 0 || file.name === undefined) return false;
else return true;
}, 'Please update or add new image.')
.refine(
(file) => ['image/jpeg', 'image/jpg', 'image/png'].includes(file?.type),
'.jpg, .jpeg, .png files are accepted.'
)
.refine((file) => file.size <= 5000000, `Max file size is 5MB.`),
});
export async function uploadProfileImageAction(
imageId: string,
prevState: any,
formData: FormData
) {
const user = await getUserMeLoader();
if (!user.ok) throw new Error('You are not authorized to perform this action.');
const userId = user.data.id;
const data = Object.fromEntries(formData);
const validatedFields = imageSchema.safeParse({
image: data.image,
});
if (!validatedFields.success) {
return {
...prevState,
zodErrors: validatedFields.error.flatten().fieldErrors,
strapiErrors: null,
data: null,
message: 'Invalid Image',
};
}
if (imageId) {
try {
await fileDeleteService(imageId);
} catch (error) {
return {
...prevState,
strapiErrors: null,
zodErrors: null,
message: 'Failed to Delete Previous Image.',
};
}
}
const fileUploadResponse = await fileUploadService(data.image);
if (!fileUploadResponse) {
return {
...prevState,
strapiErrors: null,
zodErrors: null,
message: 'Ops! Something went wrong. Please try again.',
};
}
if (fileUploadResponse.error) {
return {
...prevState,
strapiErrors: fileUploadResponse.error,
zodErrors: null,
message: 'Failed to Upload File.',
};
}
const updatedImageId = fileUploadResponse[0].id;
const payload = { image: updatedImageId };
const updateImageResponse = await mutateData(
'PUT',
`/api/users/${userId}`,
payload
);
revalidatePath('/dashboard');
return {
...prevState,
data: updateImageResponse,
zodErrors: null,
strapiErrors: null,
message: 'Image Uploaded',
};
}
```
Now that we have our backend set up let's create a new file named `file-service.ts` in the services directory and paste the following code to handle file uploads and deletions:
```typescript
import { getAuthToken } from './get-token';
import { mutateData } from './mutate-data';
import { getStrapiURL } from '../lib/utils';
export async function fileDeleteService(imageId: string) {
const authToken = await getAuthToken();
if (!authToken) throw new Error('No auth token found');
const data = await mutateData('DELETE', `/api/upload/files/${imageId}`);
return data;
}
export async function fileUploadService(image: any) {
const authToken = await getAuthToken();
if (!authToken) throw new Error('No auth token found');
const baseUrl = getStrapiURL();
const url = new URL('/api/upload', baseUrl);
const formData = new FormData();
formData.append('files', image, image.name);
try {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${authToken}` },
method: 'POST',
body: formData,
});
const dataResponse = await response.json();
return dataResponse;
} catch (error) {
console.error('Error uploading image:', error);
throw error;
}
}
```
Now that we have our backend set up let's create a new file named `update-profile-form.tsx` in the components directory and paste the following code to handle user profile updates:
```typescript
import React, { useState } from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';
import Router from 'next/router';
import Cookies from 'js-cookie';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { profileSchema } from '../actions/update-profile-action';
import { updateProfileAction } from '../actions/update-profile-action';
import { uploadProfileImageAction } from '../actions/update-image-action';
import { fileDeleteService, fileUploadService } from '../services/file-service';
const UpdateProfileForm = () => {
const router = useRouter();
const userId = router.query.id as string;
const [loading, setLoading] = useState(false);
const [imageId, setImageId] = useState<string | null>(null);
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(profileSchema),
});
const onSubmit = async (data) => {
try {
setLoading(true);
if (imageId) {
await fileDeleteService(imageId);
}
const responseData = await updateProfileAction(userId, {}, new FormData());
revalidatePath('/dashboard');
alert(responseData.message);
} catch (error) {
console.error(error);
alert('Failed to update profile.');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" placeholder="First Name" {...register('firstName')} />
{errors.firstName && <p>{errors.firstName.message}</p>}
<input type="text" placeholder="Last Name" {...register('lastName')} />
{errors.lastName && <p>{errors.lastName.message}</p>}
<textarea placeholder="Bio" {...register('bio')} />
<label htmlFor="image">Upload Image</label>
<input type="file" id="image" onChange={(e) => setImageId(null)} />
<button type="submit" disabled={loading}>Update Profile</button>
</form>
);
};
export default UpdateProfileForm;
```
Now that we have our backend set up let's create a new file named `update-image-form.tsx` in the components directory and paste the following code to handle user profile image updates:
```typescript
import React, { useState } from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';
import Router from 'next/router';
import Cookies from 'js-cookie';
import { zodResolver } from '@hookform/resolvers/zod';
import { imageSchema } from '../actions/update-image-action';
import { uploadProfileImageAction } from '../actions/update-image-action';
import { fileDeleteService, fileUploadService } from '../services/file-service';
const UpdateImageForm = () => {
const router = useRouter();
const userId = router.query.id as string;
const [loading, setLoading] = useState(false);
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(imageSchema),
});
const onSubmit = async (data) => {
try {
setLoading(true);
const responseData = await uploadProfileImageAction(null, {}, new FormData());
revalidatePath('/dashboard');
alert(responseData.message);
} catch (error) {
console.error(error);
alert('Failed to update image.');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="image">Upload Image</label>
<input type="file" id="image" onChange={(e) => setImageId(null)} />
{errors.image && <p>{errors.image.message}</p>}
<button type="submit" disabled={loading}>Update Image</button>
</form>
);
};
export default UpdateImageForm;
```
Now that we have our backend set up let's create a new file named `account.js` in the pages directory and paste the following code to handle user account data:
```javascript
import React, { useState } from 'react';
import axios from 'axios';
import Router from 'next/router';
import Cookies from 'js-cookie';
import UpdateProfileForm from '../components/update-profile-form';
import UpdateImageForm from '../components/update-image-form';
const Account = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const jwt = Cookies.get('jwt');
if (!jwt) {
Router.push('/login');
} else {
axios.get('/api/users/me', { headers: { Authorization: `Bearer ${jwt}` } })
.then((response) => setUser(response.data))
.catch((error) => console.error(error));
}
}, []);
return (
<div>
{user ? (
<div>
<h1>{user.firstName} {user.lastName}</h1>
<p>{user.bio}</p>
<img src={user.image.url} alt={user.image.alternativeText} />
<UpdateProfileForm userId={user.id} />
<UpdateImageForm userId={user.id} />
</div>
) : (
<div>Loading...</div>
)}
</div>
);
};
export default Account;
```
Now that we have our backend set up let's create a new file named `account.js` in the pages directory and paste the following code to handle user account data:
```javascript
import React, { useState } from 'react';
import axios from 'axios';
import Router from 'next/router';
import Cookies from 'js-cookie';
import UpdateProfileForm from '../components/update-profile-form';
import UpdateImageForm from '../components/update-image-form';
const Account = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const jwt = Cookies.get('jwt');
if (!jwt) {
Router.push('/login');
} else {
axios.get('/api/users/me', { headers: { Authorization: `Bearer ${jwt}` } })
.then((response) => setUser(response.data))
.catch((error) => console.error(error));
}
}, []);
return (
<div>
{user ? (
<div>
<h1>{user.firstName} {user.lastName}</h1>
<p>{user.bio}</p>
<img src={user.image.url} alt={user.image.alternativeText} />
<UpdateProfileForm userId={user.id} />
<UpdateImageForm userId={user.id} />
</div>
) : (
<div>Loading...</div>
)}
</div>
);
};
export default Account;
```
Now that we have our backend set up let's create a new file named `account.js` in the pages directory and paste the following code to handle user account data:
```javascript
import React, { useState } from 'react';
import axios from 'axios';
import Router from 'next/router';
import Cookies from 'js-cookie';
import UpdateProfileForm from '../components/update-profile-form';
import UpdateImageForm from '../components/update-image-form';
const Account = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const jwt = Cookies.get('jwt');
if (!jwt) {
Router.push('/login');
} else {
axios.get('/api/users/me', { headers: { Authorization: `Bearer ${jwt}` } })
.then((response) => setUser(response.data))
.catch((error) => console.error(error));
}
}, []);
return (
<div>
{user ? (
<div>
<h1>{user.firstName} {user.lastName}</h1>
<p>{user.bio}</p>
<img src={user.image.url} alt={user.image.alternativeText} />
<UpdateProfileForm userId={user.id} />
<UpdateImageForm userId={user.id} />
</div>
) : (
<div>Loading...</div>
)}
</div>
);
};
export default Account;
```
Apr 03, 2024
6,239 words in the original blog post.
This blog post discusses how to achieve type safety in a front-end application used for a Strapi backend. It explains that TypeScript can help when interacting with external services using their APIs, such as GraphQL and tRPC. The most commonly used APIs are still simple JSON APIs (often called REST APIs). The OpenAPI specification is introduced as a tool to describe REST APIs. The post then demonstrates how to generate TypeScript type definitions from the OpenAPI specifications using the openapi-typescript library and use them in React components. It also covers how to fetch data on both server and client sides while maintaining type safety with libraries like openapi-fetch and react-query.
Apr 01, 2024
2,686 words in the original blog post.