Unify workspace dropdowns; Update layouts (#458)

Unified Account and Workspace drop-downs; Layout updates, now header lives within the PageBody component; Sidebars now use floating variant
This commit is contained in:
Giancarlo Buomprisco
2026-03-11 14:45:42 +08:00
committed by GitHub
parent ca585e09be
commit 4bc8448a1d
530 changed files with 14398 additions and 11198 deletions

View File

@@ -24,10 +24,11 @@
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"lucide-react": "catalog:",
"next-safe-action": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"react-hook-form": "catalog:",

View File

@@ -1,11 +1,12 @@
'use client';
import { useState, useTransition } from 'react';
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
import { TriangleAlert } from 'lucide-react';
import { useAction } from 'next-safe-action/hooks';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import * as z from 'zod';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { Button } from '@kit/ui/button';
@@ -61,17 +62,28 @@ export function VerifyOtpForm({
}: VerifyOtpFormProps) {
// Track the current step (email entry or OTP verification)
const [step, setStep] = useState<'email' | 'otp'>('email');
const [isPending, startTransition] = useTransition();
// Track errors
const [error, setError] = useState<string | null>(null);
// Track verification success
const [, setVerificationSuccess] = useState(false);
const { execute: executeSendOtp, isPending } = useAction(sendOtpEmailAction, {
onSuccess: ({ data }) => {
if (data?.success) {
setStep('otp');
setError(null);
} else {
setError(data?.error || 'Failed to send OTP. Please try again.');
}
},
onError: () => {
setError('An unexpected error occurred. Please try again.');
},
});
// Email form
const emailForm = useForm({
resolver: zodResolver(SendOtpSchema),
defaultValues: {
values: {
email,
},
});
@@ -88,28 +100,14 @@ export function VerifyOtpForm({
const handleSendOtp = () => {
setError(null);
startTransition(async () => {
try {
const result = await sendOtpEmailAction({
purpose,
email,
});
if (result.success) {
setStep('otp');
} else {
setError(result.error || 'Failed to send OTP. Please try again.');
}
} catch (err) {
setError('An unexpected error occurred. Please try again.');
console.error('Error sending OTP:', err);
}
executeSendOtp({
purpose,
email,
});
};
// Handle OTP verification
const handleVerifyOtp = (data: z.infer<typeof VerifyOtpSchema>) => {
setVerificationSuccess(true);
const handleVerifyOtp = (data: z.output<typeof VerifyOtpSchema>) => {
onSuccess(data.otp);
};
@@ -124,7 +122,7 @@ export function VerifyOtpForm({
<div className="flex flex-col gap-y-2">
<p className="text-muted-foreground text-sm">
<Trans
i18nKey="common:otp.requestVerificationCodeDescription"
i18nKey="common.otp.requestVerificationCodeDescription"
values={{ email }}
/>
</p>
@@ -132,10 +130,10 @@ export function VerifyOtpForm({
<If condition={Boolean(error)}>
<Alert variant="destructive">
<ExclamationTriangleIcon className="h-4 w-4" />
<TriangleAlert className="h-4 w-4" />
<AlertTitle>
<Trans i18nKey="common:otp.errorSendingCode" />
<Trans i18nKey="common.otp.errorSendingCode" />
</AlertTitle>
<AlertDescription>{error}</AlertDescription>
@@ -153,10 +151,10 @@ export function VerifyOtpForm({
{isPending ? (
<>
<Spinner className="mr-2 h-4 w-4" />
<Trans i18nKey="common:otp.sendingCode" />
<Trans i18nKey="common.otp.sendingCode" />
</>
) : (
<Trans i18nKey="common:otp.sendVerificationCode" />
<Trans i18nKey="common.otp.sendVerificationCode" />
)}
</Button>
</div>
@@ -166,7 +164,7 @@ export function VerifyOtpForm({
<Form {...otpForm}>
<div className="flex w-full flex-col items-center gap-y-8">
<div className="text-muted-foreground text-sm">
<Trans i18nKey="common:otp.codeSentToEmail" values={{ email }} />
<Trans i18nKey="common.otp.codeSentToEmail" values={{ email }} />
</div>
<form
@@ -175,10 +173,10 @@ export function VerifyOtpForm({
>
<If condition={Boolean(error)}>
<Alert variant="destructive">
<ExclamationTriangleIcon className="h-4 w-4" />
<TriangleAlert className="h-4 w-4" />
<AlertTitle>
<Trans i18nKey="common:error" />
<Trans i18nKey="common.error" />
</AlertTitle>
<AlertDescription>{error}</AlertDescription>
@@ -212,7 +210,7 @@ export function VerifyOtpForm({
</FormControl>
<FormDescription>
<Trans i18nKey="common:otp.enterCodeFromEmail" />
<Trans i18nKey="common.otp.enterCodeFromEmail" />
</FormDescription>
<FormMessage />
</FormItem>
@@ -229,7 +227,7 @@ export function VerifyOtpForm({
disabled={isPending}
onClick={() => setStep('email')}
>
<Trans i18nKey="common:otp.requestNewCode" />
<Trans i18nKey="common.otp.requestNewCode" />
</Button>
<Button
@@ -240,10 +238,10 @@ export function VerifyOtpForm({
{isPending ? (
<>
<Spinner className="mr-2 h-4 w-4" />
<Trans i18nKey="common:otp.verifying" />
<Trans i18nKey="common.otp.verifying" />
</>
) : (
<Trans i18nKey="common:otp.verifyCode" />
<Trans i18nKey="common.otp.verifyCode" />
)}
</Button>
</div>

View File

@@ -1,4 +1,4 @@
import { z } from 'zod';
import * as z from 'zod';
import { renderOtpEmail } from '@kit/email-templates';
import { getMailer } from '@kit/mailers';
@@ -6,14 +6,14 @@ import { getLogger } from '@kit/shared/logger';
const EMAIL_SENDER = z
.string({
required_error: 'EMAIL_SENDER is required',
error: 'EMAIL_SENDER is required',
})
.min(1)
.parse(process.env.EMAIL_SENDER);
const PRODUCT_NAME = z
.string({
required_error: 'PRODUCT_NAME is required',
error: 'PRODUCT_NAME is required',
})
.min(1)
.parse(process.env.NEXT_PUBLIC_PRODUCT_NAME);

View File

@@ -1,8 +1,8 @@
'use server';
import { z } from 'zod';
import * as z from 'zod';
import { enhanceAction } from '@kit/next/actions';
import { authActionClient } from '@kit/next/safe-action';
import { getLogger } from '@kit/shared/logger';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
@@ -25,8 +25,9 @@ const SendOtpEmailSchema = z.object({
/**
* Server action to generate an OTP and send it via email
*/
export const sendOtpEmailAction = enhanceAction(
async function (data: z.infer<typeof SendOtpEmailSchema>, user) {
export const sendOtpEmailAction = authActionClient
.schema(SendOtpEmailSchema)
.action(async ({ parsedInput: data, ctx: { user } }) => {
const logger = await getLogger();
const ctx = { name: 'send-otp-email', userId: user.id };
const email = user.email;
@@ -87,9 +88,4 @@ export const sendOtpEmailAction = enhanceAction(
error instanceof Error ? error.message : 'Failed to send OTP email',
};
}
},
{
schema: SendOtpEmailSchema,
auth: true,
},
);
});