- Getting Started
- General Information
- About the Template
- About NextAuth
- Middleware
- Cypress E2E Testing
- Standard Date Format - dayjs
- Using dimensions for responsive design
This is a Next.js project bootstrapped with create-next-app.
First, run the development server:
npm run dev
# or
yarn dev
# or
pnpm devThis template includes the following functionalities:
- Login
- Signup
- Email validation
- Password recovery
- Change password
And the next libraries:
- Next.js
- Tailwind CSS
- PrimeReact
- NextAuth
- Zustand
- lenis
- formik
- framer-motion
- swr
- usehooks-ts
- dayjs
This template is a Next.js template that includes a basic authentication system using NextAuth and Zustand. It also includes a custom hook called useParamsHandler that allows you to handle URL parameters in the URL. The template also includes a custom hook called usePressKey that allows you to handle keyboard events. For auth functionalities, the template uses NextAuth, which is a popular authentication library for Next.js.
NextAuth is a popular authentication library for Next.js. It provides a simple and easy-to-use API for handling authentication and authorization in your Next.js applications. With NextAuth, you can easily add authentication to your Next.js applications, including login, signup, password recovery, and more.
The next-auth configuration file is located in the src/pages/api/auth/[...nextauth].ts file. This file contains the configuration for the authentication system, including the providers, callbacks, and other settings.
NextAuth supports various providers, including email, password, OAuth, and more. The providers are configured in the src/pages/api/auth/[...nextauth].ts file. For this template, the providers are configured to use the credentials strategy, which allows users to sign in using their email and password. Additionally, this setup is combined with the JWT strategy to ensure token-based authentication, enabling secure and stateless session management for the users. The JWT tokens are issued and verified when users sign in using their credentials, allowing further API requests to be authenticated with these tokens.
NextAuth provides callbacks for various events, such as sign in, sign out, and error. The callbacks are configured in the src/pages/api/auth/[...nextauth].ts file.
NextAuth provides a session object that contains information about the user's session, such as the user ID, access token, and refresh token. The session object is available in the src/pages/api/auth/[...nextauth].ts file.
This template includes a custom middleware function designed to manage access control based on user authentication status. The middleware retrieves the JWT token using NextAuth and determines whether users have access to public or private routes. Below is a summary of how the middleware works:
- Token Retrieval: The middleware attempts to retrieve the token using getToken from NextAuth.
- Public and Private Paths:
- The template defines certain public paths (e.g., /login, /signup) that do not require authentication.
- Static resources (e.g., images, fonts) are also publicly accessible.
- Redirection Logic: If an authenticated user tries to access public client paths like /login or /signup, they will be redirected to the home page (/). If no token is present and the user tries to access a private route, they will be redirected to the login page (/login).
- Profile Completion Check (optional): The middleware can be extended to check if a user's profile is complete and redirect them to a profile completion page if necessary. This middleware ensures that unauthenticated users are restricted from accessing private pages while authenticated users are seamlessly redirected to the appropriate sections of the application.
The middleware is located in the src/middleware.ts file and is responsible for handling authentication and authorization for different routes in the application.
Your Cypress project follows this folder structure:
cypress
├── e2e
│ └── **.cy.ts // E2E test files in TypeScript format
├── fixtures
│ └── example.ts // Static data for use in tests
└── support
├── commands.ts // Custom Cypress commands
└── e2e.ts // Support configuration for E2E tests
- cypress-dotenv to load environment variables from a .env file.
- cypress-file-upload to handle file uploads in tests, enabling the use of cy.upload().
import 'cypress-file-upload'
describe('Profile Information Update', () => {
before(function () {
// Load data from fixture
cy.fixture('example').then(function (data) {
this.data = data
})
})
it('logs in and updates profile information', function () {
// Base URLs
const baseUrl = Cypress.config('baseUrl')
const profileUrl = `${baseUrl}/profile`
// Dynamic variables for profile updates
const firstName = 'TestFirst' + Math.floor(Math.random() * 1000)
const lastName = 'TestLast' + Math.floor(Math.random() * 1000)
const phone = '+12345' + Math.floor(Math.random() * 1000)
// Intercept profile update request
cy.intercept('PATCH', '/api/users/me').as('updateProfile')
// Log in using custom command (requires setup in commands.ts)
cy.login(this.data.email, this.data.password).then(() => {
cy.url().should('eq', `${baseUrl}/dashboard`)
cy.get('.ProfileButton').should('be.visible').click()
})
// Navigate to profile page
cy.get('[href="/profile"]').click()
cy.url().should('eq', profileUrl)
// Update profile fields
cy.get('#first_name').clear().type(firstName)
cy.get('#last_name').clear().type(lastName)
cy.get('#phone').clear().type(phone)
// Submit changes
cy.get('.saveButton').click()
// Wait for profile update request
cy.wait('@updateProfile').its('response.statusCode').should('eq', 200)
// Reload page and verify updates
cy.reload()
cy.get('#first_name').should('have.value', firstName)
cy.get('#last_name').should('have.value', lastName)
cy.get('#phone').should('have.value', phone)
})
})describe('Navigation and Route Protection', () => {
const baseUrl = Cypress.config('baseUrl')
const protectedRoute = `${baseUrl}/`
const loginRoute = `${baseUrl}/login`
before(() => {
// Load fixture data if needed, e.g., user credentials
cy.fixture('userData').then(function (data) {
this.data = data
})
})
context('Unauthenticated user', () => {
it('should redirect to login page when trying to access a protected route', function () {
cy.visit(protectedRoute)
cy.url().should('eq', loginRoute) // Assert redirection to login
})
})
context('Authenticated user', () => {
beforeEach(function () {
// Log in using custom command and store session
cy.login(this.data.email, this.data.password) // Ensure this command is set up in commands.ts
})
it('should access protected route after login', () => {
cy.visit(protectedRoute)
cy.url().should('eq', protectedRoute) // Assert the user is on the protected route
cy.contains('Powered by Linkchar') // Customize based on your dashboard content
})
it('should redirect to home when visiting login page as authenticated user', () => {
cy.visit(loginRoute)
cy.url().should('eq', `${baseUrl}/`) // Assert redirection to home page
})
})
})Explanation of Key Sections
- Fixture Loading: Loads static data from a fixture file to make tests reusable.
- Dynamic Data Generation: Adds unique values for fields to simulate different data each test run.
- Interception: Captures network requests, allowing us to validate the response.
- Custom Commands: A cy.login() command (created in commands.ts) abstracts login steps, improving code readability.
- Assertions: Verifies that the profile page displays updated values after submission.
This test flow covers typical user actions like loading data, interacting with form fields, intercepting network requests, and verifying responses.
In the component or module where you need to work with UTC dates, import dayjs and the utc plugin, then extend dayjs with the plugin:
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
// Extend dayjs with the UTC plugin
dayjs.extend(utc);const currentUTCDate = dayjs().utc().format();
console.log(currentUTCDate); // Displays the current date and time in UTC (ISO format)If you have a local date and need to convert it to UTC, you can use this approach:
const localDate = '2024-10-03T15:30:00'; // Local date
const utcDate = dayjs(localDate).utc().format();
console.log(utcDate); // Outputs the date in UTCTo standardize the UTC date into a specific format, use the following code:
const formattedUTCDate = dayjs().utc().format('YYYY-MM-DD HH:mm:ss');
console.log(formattedUTCDate); // For example: "2024-10-03 18:30:00"For more information about dayjs, you can refer to the official documentation: https://day.js.org/docs/en/display/format
When developing responsive designs, especially for mobile compatibility, it's essential to select the appropriate unit of measurement for viewport height. Specifically, the svh unit is recommended for handling height in mobile web browsers like Safari on iOS, which can present challenges with traditional vh units.
- vh (Viewport Height): Represents 1% of the viewport height. However, it does not account for dynamic UI changes like the appearance of the address bar or keyboard on mobile browsers. When these UI elements appear, the visible area shrinks, causing potential layout shifts.
- dvh (Dynamic Viewport Height): Adjusts based on dynamic changes in the viewport size. For instance, if the browser UI (address bar or keyboard) appears, the dvh value changes to reflect the newly available height. However, when using dvh, it can lead to undesired overflow behavior, particularly when a keyboard pops up. The scroll may extend beyond the container, which isn't ideal for keeping elements confined.
- svh (Small Viewport Height): Stays constant regardless of dynamic changes in UI elements, providing a stable height based on the smallest viewport height available. This makes it preferable for mobile layouts because it ensures the content is contained within the viewport, even if an on-screen keyboard or other UI elements appear.
In summary, using svh offers better control and consistency for mobile layouts. Unlike dvh, svh avoids overflow issues when input fields are active, making it the ideal choice for designing consistent and visually stable interfaces on mobile devices.
However, for layouts without interactive elements, such as text-only sections or image galleries, dvh can be preferable since it adjusts dynamically to changes in viewport height. For layouts that include interactive elements like input, textarea, or select, svh is recommended to ensure the layout remains stable even when the on-screen keyboard or other UI elements appear.
For more information on viewport units: https://web.dev/blog/viewport-units/