Revert "Unify workspace dropdowns; Update layouts (#458)"

This reverts commit 4bc8448a1d.
This commit is contained in:
gbuomprisco
2026-03-11 14:47:47 +08:00
parent 4bc8448a1d
commit 4912e402a3
530 changed files with 11182 additions and 14382 deletions

View File

@@ -1,17 +1,18 @@
'use server';
import 'server-only';
import { redirect } from 'next/navigation';
import { authActionClient } from '@kit/next/safe-action';
import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { CreateTeamSchema } from '../../schema/create-team.schema';
import { createAccountCreationPolicyEvaluator } from '../policies';
import { createCreateTeamAccountService } from '../services/create-team-account.service';
export const createTeamAccountAction = authActionClient
.schema(CreateTeamSchema)
.action(async ({ parsedInput: { name, slug }, ctx: { user } }) => {
export const createTeamAccountAction = enhanceAction(
async ({ name, slug }, user) => {
const logger = await getLogger();
const service = createCreateTeamAccountService();
@@ -60,7 +61,7 @@ export const createTeamAccountAction = authActionClient
if (error === 'duplicate_slug') {
return {
error: true,
message: 'teams.duplicateSlugError',
message: 'teams:duplicateSlugError',
};
}
@@ -69,4 +70,8 @@ export const createTeamAccountAction = authActionClient
const accountHomePath = '/home/' + data.slug;
redirect(accountHomePath);
});
},
{
schema: CreateTeamSchema,
},
);

View File

@@ -4,7 +4,7 @@ import { redirect } from 'next/navigation';
import type { SupabaseClient } from '@supabase/supabase-js';
import { authActionClient } from '@kit/next/safe-action';
import { enhanceAction } from '@kit/next/actions';
import { createOtpApi } from '@kit/otp';
import { getLogger } from '@kit/shared/logger';
import type { Database } from '@kit/supabase/database';
@@ -16,11 +16,14 @@ import { createDeleteTeamAccountService } from '../services/delete-team-account.
const enableTeamAccountDeletion =
process.env.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION === 'true';
export const deleteTeamAccountAction = authActionClient
.schema(DeleteTeamAccountSchema)
.action(async ({ parsedInput: params, ctx: { user } }) => {
export const deleteTeamAccountAction = enhanceAction(
async (formData: FormData, user) => {
const logger = await getLogger();
const params = DeleteTeamAccountSchema.parse(
Object.fromEntries(formData.entries()),
);
const otpService = createOtpApi(getSupabaseServerClient());
const otpResult = await otpService.verifyToken({
@@ -54,8 +57,12 @@ export const deleteTeamAccountAction = authActionClient
logger.info(ctx, `Team account request successfully sent`);
redirect('/home');
});
return redirect('/home');
},
{
auth: true,
},
);
async function deleteTeamAccount(params: {
accountId: string;

View File

@@ -3,15 +3,17 @@
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { authActionClient } from '@kit/next/safe-action';
import { enhanceAction } from '@kit/next/actions';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import { LeaveTeamAccountSchema } from '../../schema/leave-team-account.schema';
import { createLeaveTeamAccountService } from '../services/leave-team-account.service';
export const leaveTeamAccountAction = authActionClient
.schema(LeaveTeamAccountSchema)
.action(async ({ parsedInput: params, ctx: { user } }) => {
export const leaveTeamAccountAction = enhanceAction(
async (formData: FormData, user) => {
const body = Object.fromEntries(formData.entries());
const params = LeaveTeamAccountSchema.parse(body);
const service = createLeaveTeamAccountService(
getSupabaseServerAdminClient(),
);
@@ -23,5 +25,7 @@ export const leaveTeamAccountAction = authActionClient
revalidatePath('/home/[account]', 'layout');
redirect('/home');
});
return redirect('/home');
},
{},
);

View File

@@ -2,15 +2,14 @@
import { redirect } from 'next/navigation';
import { authActionClient } from '@kit/next/safe-action';
import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { UpdateTeamNameSchema } from '../../schema/update-team-name.schema';
export const updateTeamAccountName = authActionClient
.schema(UpdateTeamNameSchema)
.action(async ({ parsedInput: params }) => {
export const updateTeamAccountName = enhanceAction(
async (params) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const { name, path, slug, newSlug } = params;
@@ -41,7 +40,7 @@ export const updateTeamAccountName = authActionClient
if (error.code === '23505') {
return {
success: false,
error: 'teams.duplicateSlugError',
error: 'teams:duplicateSlugError',
};
}
@@ -61,4 +60,8 @@ export const updateTeamAccountName = authActionClient
}
return { success: true };
});
},
{
schema: UpdateTeamNameSchema,
},
);

View File

@@ -3,9 +3,9 @@
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import * as z from 'zod';
import { z } from 'zod';
import { authActionClient } from '@kit/next/safe-action';
import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { Database } from '@kit/supabase/database';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
@@ -26,15 +26,8 @@ import { createAccountPerSeatBillingService } from '../services/account-per-seat
* @name createInvitationsAction
* @description Creates invitations for inviting members.
*/
export const createInvitationsAction = authActionClient
.schema(
InviteMembersSchema.and(
z.object({
accountSlug: z.string().min(1),
}),
),
)
.action(async ({ parsedInput: params, ctx: { user } }) => {
export const createInvitationsAction = enhanceAction(
async (params, user) => {
const logger = await getLogger();
logger.info(
@@ -123,15 +116,22 @@ export const createInvitationsAction = authActionClient
success: false,
};
}
});
},
{
schema: InviteMembersSchema.and(
z.object({
accountSlug: z.string().min(1),
}),
),
},
);
/**
* @name deleteInvitationAction
* @description Deletes an invitation specified by the invitation ID.
*/
export const deleteInvitationAction = authActionClient
.schema(DeleteInvitationSchema)
.action(async ({ parsedInput: data }) => {
export const deleteInvitationAction = enhanceAction(
async (data) => {
const client = getSupabaseServerClient();
const service = createAccountInvitationsService(client);
@@ -143,15 +143,18 @@ export const deleteInvitationAction = authActionClient
return {
success: true,
};
});
},
{
schema: DeleteInvitationSchema,
},
);
/**
* @name updateInvitationAction
* @description Updates an invitation.
*/
export const updateInvitationAction = authActionClient
.schema(UpdateInvitationSchema)
.action(async ({ parsedInput: invitation }) => {
export const updateInvitationAction = enhanceAction(
async (invitation) => {
const client = getSupabaseServerClient();
const service = createAccountInvitationsService(client);
@@ -162,18 +165,23 @@ export const updateInvitationAction = authActionClient
return {
success: true,
};
});
},
{
schema: UpdateInvitationSchema,
},
);
/**
* @name acceptInvitationAction
* @description Accepts an invitation to join a team.
*/
export const acceptInvitationAction = authActionClient
.schema(AcceptInvitationSchema)
.action(async ({ parsedInput: data, ctx: { user } }) => {
export const acceptInvitationAction = enhanceAction(
async (data: FormData, user) => {
const client = getSupabaseServerClient();
const { inviteToken, nextPath } = data;
const { inviteToken, nextPath } = AcceptInvitationSchema.parse(
Object.fromEntries(data),
);
// create the services
const perSeatBillingService = createAccountPerSeatBillingService(client);
@@ -197,17 +205,19 @@ export const acceptInvitationAction = authActionClient
// Increase the seats for the account
await perSeatBillingService.increaseSeats(accountId);
redirect(nextPath);
});
return redirect(nextPath);
},
{},
);
/**
* @name renewInvitationAction
* @description Renews an invitation.
*/
export const renewInvitationAction = authActionClient
.schema(RenewInvitationSchema)
.action(async ({ parsedInput: { invitationId } }) => {
export const renewInvitationAction = enhanceAction(
async (params) => {
const client = getSupabaseServerClient();
const { invitationId } = RenewInvitationSchema.parse(params);
const service = createAccountInvitationsService(client);
@@ -219,7 +229,11 @@ export const renewInvitationAction = authActionClient
return {
success: true,
};
});
},
{
schema: RenewInvitationSchema,
},
);
function revalidateMemberPage() {
revalidatePath('/home/[account]/members', 'page');
@@ -233,7 +247,7 @@ function revalidateMemberPage() {
* @param accountId - The account ID (already fetched to avoid duplicate queries).
*/
async function evaluateInvitationsPolicies(
params: z.output<typeof InviteMembersSchema> & { accountSlug: string },
params: z.infer<typeof InviteMembersSchema> & { accountSlug: string },
user: JWTUserData,
accountId: string,
) {
@@ -268,7 +282,7 @@ async function evaluateInvitationsPolicies(
async function checkInvitationPermissions(
accountId: string,
userId: string,
invitations: z.output<typeof InviteMembersSchema>['invitations'],
invitations: z.infer<typeof InviteMembersSchema>['invitations'],
): Promise<{
allowed: boolean;
reason?: string;

View File

@@ -2,7 +2,7 @@
import { revalidatePath } from 'next/cache';
import { authActionClient } from '@kit/next/safe-action';
import { enhanceAction } from '@kit/next/actions';
import { createOtpApi } from '@kit/otp';
import { getLogger } from '@kit/shared/logger';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
@@ -17,9 +17,8 @@ import { createAccountMembersService } from '../services/account-members.service
* @name removeMemberFromAccountAction
* @description Removes a member from an account.
*/
export const removeMemberFromAccountAction = authActionClient
.schema(RemoveMemberSchema)
.action(async ({ parsedInput: { accountId, userId } }) => {
export const removeMemberFromAccountAction = enhanceAction(
async ({ accountId, userId }) => {
const client = getSupabaseServerClient();
const service = createAccountMembersService(client);
@@ -32,15 +31,18 @@ export const removeMemberFromAccountAction = authActionClient
revalidatePath('/home/[account]', 'layout');
return { success: true };
});
},
{
schema: RemoveMemberSchema,
},
);
/**
* @name updateMemberRoleAction
* @description Updates the role of a member in an account.
*/
export const updateMemberRoleAction = authActionClient
.schema(UpdateMemberRoleSchema)
.action(async ({ parsedInput: data }) => {
export const updateMemberRoleAction = enhanceAction(
async (data) => {
const client = getSupabaseServerClient();
const service = createAccountMembersService(client);
const adminClient = getSupabaseServerAdminClient();
@@ -52,16 +54,19 @@ export const updateMemberRoleAction = authActionClient
revalidatePath('/home/[account]', 'layout');
return { success: true };
});
},
{
schema: UpdateMemberRoleSchema,
},
);
/**
* @name transferOwnershipAction
* @description Transfers the ownership of an account to another member.
* Requires OTP verification for security.
*/
export const transferOwnershipAction = authActionClient
.schema(TransferOwnershipConfirmationSchema)
.action(async ({ parsedInput: data, ctx: { user } }) => {
export const transferOwnershipAction = enhanceAction(
async (data, user) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
@@ -132,4 +137,8 @@ export const transferOwnershipAction = authActionClient
return {
success: true,
};
});
},
{
schema: TransferOwnershipConfirmationSchema,
},
);

View File

@@ -1,6 +1,6 @@
import type { SupabaseClient } from '@supabase/supabase-js';
import * as z from 'zod';
import { z } from 'zod';
import type { Database } from '@kit/supabase/database';
import { JWTUserData } from '@kit/supabase/types';
@@ -29,7 +29,7 @@ class InvitationContextBuilder {
* Build policy context for invitation evaluation with optimized parallel loading
*/
async buildContext(
params: z.output<typeof InviteMembersSchema> & { accountSlug: string },
params: z.infer<typeof InviteMembersSchema> & { accountSlug: string },
user: JWTUserData,
): Promise<FeaturePolicyInvitationContext> {
// Fetch all data in parallel for optimal performance
@@ -43,7 +43,7 @@ class InvitationContextBuilder {
* (avoids duplicate account lookup)
*/
async buildContextWithAccountId(
params: z.output<typeof InviteMembersSchema> & { accountSlug: string },
params: z.infer<typeof InviteMembersSchema> & { accountSlug: string },
user: JWTUserData,
accountId: string,
): Promise<FeaturePolicyInvitationContext> {

View File

@@ -20,8 +20,8 @@ export const subscriptionRequiredInvitationsPolicy =
if (!subscription || !subscription.active) {
return deny({
code: 'SUBSCRIPTION_REQUIRED',
message: 'teams.policyErrors.subscriptionRequired',
remediation: 'teams.policyRemediation.subscriptionRequired',
message: 'teams:policyErrors.subscriptionRequired',
remediation: 'teams:policyRemediation.subscriptionRequired',
});
}
@@ -55,8 +55,8 @@ export const paddleBillingInvitationsPolicy =
if (hasPerSeatItems) {
return deny({
code: 'PADDLE_TRIAL_RESTRICTION',
message: 'teams.policyErrors.paddleTrialRestriction',
remediation: 'teams.policyRemediation.paddleTrialRestriction',
message: 'teams:policyErrors.paddleTrialRestriction',
remediation: 'teams:policyRemediation.paddleTrialRestriction',
});
}
}

View File

@@ -1,6 +1,6 @@
import { SupabaseClient } from '@supabase/supabase-js';
import * as z from 'zod';
import { z } from 'zod';
import { getLogger } from '@kit/shared/logger';
import { Database, Tables } from '@kit/supabase/database';
@@ -18,22 +18,22 @@ const env = z
.object({
invitePath: z
.string({
error: 'The property invitePath is required',
required_error: 'The property invitePath is required',
})
.min(1),
siteURL: z
.string({
error: 'NEXT_PUBLIC_SITE_URL is required',
required_error: 'NEXT_PUBLIC_SITE_URL is required',
})
.min(1),
productName: z
.string({
error: 'NEXT_PUBLIC_PRODUCT_NAME is required',
required_error: 'NEXT_PUBLIC_PRODUCT_NAME is required',
})
.min(1),
emailSender: z
.string({
error: 'EMAIL_SENDER is required',
required_error: 'EMAIL_SENDER is required',
})
.min(1),
})

View File

@@ -3,7 +3,7 @@ import 'server-only';
import { SupabaseClient } from '@supabase/supabase-js';
import { addDays, formatISO } from 'date-fns';
import * as z from 'zod';
import { z } from 'zod';
import { getLogger } from '@kit/shared/logger';
import { Database } from '@kit/supabase/database';
@@ -37,7 +37,7 @@ class AccountInvitationsService {
* @description Removes an invitation from the database.
* @param params
*/
async deleteInvitation(params: z.output<typeof DeleteInvitationSchema>) {
async deleteInvitation(params: z.infer<typeof DeleteInvitationSchema>) {
const logger = await getLogger();
const ctx = {
@@ -70,7 +70,7 @@ class AccountInvitationsService {
* @param params
* @description Updates an invitation in the database.
*/
async updateInvitation(params: z.output<typeof UpdateInvitationSchema>) {
async updateInvitation(params: z.infer<typeof UpdateInvitationSchema>) {
const logger = await getLogger();
const ctx = {
@@ -107,7 +107,7 @@ class AccountInvitationsService {
}
async validateInvitation(
invitation: z.output<typeof InviteMembersSchema>['invitations'][number],
invitation: z.infer<typeof InviteMembersSchema>['invitations'][number],
accountSlug: string,
) {
const { data: members, error } = await this.client.rpc(
@@ -141,7 +141,7 @@ class AccountInvitationsService {
invitations,
invitedBy,
}: {
invitations: z.output<typeof InviteMembersSchema>['invitations'];
invitations: z.infer<typeof InviteMembersSchema>['invitations'];
accountSlug: string;
invitedBy: string;
}) {

View File

@@ -2,7 +2,7 @@ import 'server-only';
import { SupabaseClient } from '@supabase/supabase-js';
import * as z from 'zod';
import { z } from 'zod';
import { getLogger } from '@kit/shared/logger';
import { Database } from '@kit/supabase/database';
@@ -26,7 +26,7 @@ class AccountMembersService {
* @description Removes a member from an account.
* @param params
*/
async removeMemberFromAccount(params: z.output<typeof RemoveMemberSchema>) {
async removeMemberFromAccount(params: z.infer<typeof RemoveMemberSchema>) {
const logger = await getLogger();
const ctx = {
@@ -75,7 +75,7 @@ class AccountMembersService {
* @param adminClient
*/
async updateMemberRole(
params: z.output<typeof UpdateMemberRoleSchema>,
params: z.infer<typeof UpdateMemberRoleSchema>,
adminClient: SupabaseClient<Database>,
) {
const logger = await getLogger();
@@ -145,7 +145,7 @@ class AccountMembersService {
* @param adminClient
*/
async transferOwnership(
params: z.output<typeof TransferOwnershipConfirmationSchema>,
params: z.infer<typeof TransferOwnershipConfirmationSchema>,
adminClient: SupabaseClient<Database>,
) {
const logger = await getLogger();

View File

@@ -2,7 +2,7 @@ import 'server-only';
import { SupabaseClient } from '@supabase/supabase-js';
import * as z from 'zod';
import { z } from 'zod';
import { getLogger } from '@kit/shared/logger';
import { Database } from '@kit/supabase/database';
@@ -32,7 +32,7 @@ class LeaveTeamAccountService {
* @description Leave a team account
* @param params
*/
async leaveTeamAccount(params: z.output<typeof Schema>) {
async leaveTeamAccount(params: z.infer<typeof Schema>) {
const logger = await getLogger();
const ctx = {