Next.js Supabase V3 (#463)

Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
This commit is contained in:
Giancarlo Buomprisco
2026-03-24 13:40:38 +08:00
committed by GitHub
parent 4912e402a3
commit 7ebff31475
840 changed files with 71395 additions and 20095 deletions

View File

@@ -38,10 +38,12 @@ test.describe('Account Settings', () => {
await Promise.all([request, response]);
await page.locator('[data-test="workspace-dropdown-trigger"]').click();
await expect(account.getProfileName()).toHaveText(name);
});
test('user can update their email', async ({ page }) => {
test('user can update their email', async () => {
const email = account.auth.createRandomEmail();
await account.updateEmail(email);

View File

@@ -34,17 +34,17 @@ test.describe('Admin', () => {
await page.goto('/admin');
// Check all stat cards are present
await expect(page.getByRole('heading', { name: 'Users' })).toBeVisible();
await expect(page.getByText('Users', { exact: true })).toBeVisible();
await expect(
page.getByRole('heading', { name: 'Team Accounts' }),
page.getByText('Team Accounts', { exact: true }),
).toBeVisible();
await expect(
page.getByRole('heading', { name: 'Paying Customers' }),
page.getByText('Paying Customers', { exact: true }),
).toBeVisible();
await expect(page.getByRole('heading', { name: 'Trials' })).toBeVisible();
await expect(page.getByText('Trials', { exact: true })).toBeVisible();
// Verify stat values are numbers
const stats = await page.$$('.text-3xl.font-bold');
@@ -351,5 +351,5 @@ async function selectAccount(page: Page, email: string) {
await link.click();
await page.waitForURL(/\/admin\/accounts\/[^\/]+/);
await page.waitForURL(/\/admin\/accounts\/[^/]+/);
}

View File

@@ -1,9 +1,10 @@
import { test } from '@playwright/test';
import { join } from 'node:path';
import { cwd } from 'node:process';
import { AuthPageObject } from './authentication/auth.po';
import { join } from 'node:path';
import { cwd } from 'node:process';
const testAuthFile = join(cwd(), '.auth/test@makerkit.dev.json');
const ownerAuthFile = join(cwd(), '.auth/owner@makerkit.dev.json');
const superAdminAuthFile = join(cwd(), '.auth/super-admin@makerkit.dev.json');

View File

@@ -31,8 +31,17 @@ export class AuthPageObject {
}
async signOut() {
await this.page.click('[data-test="account-dropdown-trigger"]');
await this.page.click('[data-test="account-dropdown-sign-out"]');
const trigger = this.page.locator(
'[data-test="workspace-dropdown-trigger"], [data-test="account-dropdown-trigger"]',
);
await trigger.click();
const signOutButton = this.page.locator(
'[data-test="workspace-sign-out"], [data-test="account-dropdown-sign-out"]',
);
await signOutButton.click();
}
async signIn(params: { email: string; password: string }) {

View File

@@ -4,7 +4,7 @@ import { expect, test } from '@playwright/test';
test.describe('Healthcheck endpoint', () => {
test('returns healthy status', async ({ request }) => {
const response = await request.get('/healthcheck');
const response = await request.get('/api/healthcheck');
expect(response.status()).toBe(200);

View File

@@ -46,7 +46,7 @@ export class InvitationsPageObject {
`[data-test="invite-member-form-item"]:nth-child(${nth}) [data-test="role-selector-trigger"]`,
);
await this.page.click(`[data-test="role-option-${invite.role}"]`);
await this.page.getByRole('option', { name: invite.role }).click();
if (index < invites.length - 1) {
await form.locator('[data-test="add-new-invite-button"]').click();

View File

@@ -36,13 +36,13 @@ export class TeamAccountsPageObject {
}
getTeamFromSelector(teamName: string) {
return this.page.locator(`[data-test="account-selector-team"]`, {
return this.page.locator('[data-test="workspace-team-item"]', {
hasText: teamName,
});
}
getTeams() {
return this.page.locator('[data-test="account-selector-team"]');
return this.page.locator('[data-test="workspace-team-item"]');
}
goToSettings() {
@@ -83,10 +83,11 @@ export class TeamAccountsPageObject {
openAccountsSelector() {
return expect(async () => {
await this.page.click('[data-test="account-selector-trigger"]');
await this.page.click('[data-test="workspace-dropdown-trigger"]');
await this.page.click('[data-test="workspace-switch-submenu"]');
return expect(
this.page.locator('[data-test="account-selector-content"]'),
this.page.locator('[data-test="workspace-switch-content"]'),
).toBeVisible();
}).toPass();
}
@@ -115,7 +116,7 @@ export class TeamAccountsPageObject {
async createTeam({ teamName, slug } = this.createTeamName()) {
await this.openAccountsSelector();
await this.page.click('[data-test="create-team-account-trigger"]');
await this.page.click('[data-test="create-team-trigger"]');
await this.page.fill(
'[data-test="create-team-form"] [data-test="team-name-input"]',
@@ -140,14 +141,13 @@ export class TeamAccountsPageObject {
await this.openAccountsSelector();
await expect(this.getTeamFromSelector(teamName)).toBeVisible();
// Close the selector
await this.page.keyboard.press('Escape');
await this.closeAccountsSelector();
}
async createTeamWithNonLatinName(teamName: string, slug: string) {
await this.openAccountsSelector();
await this.page.click('[data-test="create-team-account-trigger"]');
await this.page.click('[data-test="create-team-trigger"]');
await this.page.fill(
'[data-test="create-team-form"] [data-test="team-name-input"]',
@@ -177,8 +177,15 @@ export class TeamAccountsPageObject {
await this.openAccountsSelector();
await expect(this.getTeamFromSelector(teamName)).toBeVisible();
// Close the selector
await this.page.keyboard.press('Escape');
await this.closeAccountsSelector();
}
async closeAccountsSelector() {
await this.page.locator('body').click({ position: { x: 0, y: 0 } });
await expect(
this.page.locator('[data-test="workspace-switch-content"]'),
).toBeHidden();
}
getSlugField() {
@@ -207,11 +214,10 @@ export class TeamAccountsPageObject {
}
async deleteAccount(email: string) {
await this.page.click('[data-test="delete-team-trigger"]');
await this.otp.completeOtpVerification(email);
await expect(async () => {
await this.page.click('[data-test="delete-team-trigger"]');
await this.otp.completeOtpVerification(email);
const click = this.page.click(
'[data-test="delete-team-form-confirm-button"]',
);

View File

@@ -88,7 +88,7 @@ test.describe('Team Accounts', () => {
await teamAccounts.createTeam();
await teamAccounts.openAccountsSelector();
await page.click('[data-test="create-team-account-trigger"]');
await page.click('[data-test="create-team-trigger"]');
await teamAccounts.tryCreateTeam('billing');
@@ -202,7 +202,7 @@ test.describe('Team Accounts', () => {
// Use non-Latin name to trigger the slug field visibility
await teamAccounts.openAccountsSelector();
await page.click('[data-test="create-team-account-trigger"]');
await page.click('[data-test="create-team-trigger"]');
await page.fill(
'[data-test="create-team-form"] [data-test="team-name-input"]',

View File

@@ -1,6 +1,5 @@
import { expect, test } from '@playwright/test';
import { AuthPageObject } from '../authentication/auth.po';
import { TeamBillingPageObject } from './team-billing.po';
test.describe('Team Billing', () => {

View File

@@ -38,9 +38,9 @@ export class BillingPageObject {
// wait a bit for the webhook to be processed
await this.page.waitForTimeout(1000);
return this.page
.locator('[data-test="checkout-success-back-link"]')
.click();
await this.page.locator('[data-test="checkout-success-back-link"]').click();
await this.page.waitForURL('**/billing');
}
proceedToCheckout() {