diff --git a/app/(account)/sign-in/page.tsx b/app/(account)/sign-in/page.tsx
new file mode 100644
index 0000000000..856084d0fe
--- /dev/null
+++ b/app/(account)/sign-in/page.tsx
@@ -0,0 +1,31 @@
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle
+} from '@/ui-components/ui/card';
+import Link from 'next/link';
+import {SignInForm} from "@/components/account/sign-in-form";
+
+export default async function SignIn() {
+ return (
+
+
+
+ Sign in
+ Sign in to your account
+
+
+
+
+
+
+ Forgot your password?
+
+
+
+
+ );
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 169c5a90ee..ff87afd4db 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -6,9 +6,10 @@ import { getActiveChannel, getActiveOrder } from 'lib/vendure';
import { ensureStartsWith } from 'lib/utils';
import { cookies } from 'next/headers';
import { ReactNode } from 'react';
-import { Toaster } from 'sonner';
import './globals.css';
import { ChannelProvider } from '../components/cart/channel-context';
+import { Toaster } from '@/ui-components/ui/toaster';
+import { ToastProvider } from '@/ui-components/ui/toast';
const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env;
const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
@@ -44,19 +45,21 @@ export default async function RootLayout({ children }: { children: ReactNode })
const activeChannel = getActiveChannel();
return (
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
);
}
diff --git a/components/account/actions.ts b/components/account/actions.ts
new file mode 100644
index 0000000000..95fbc22555
--- /dev/null
+++ b/components/account/actions.ts
@@ -0,0 +1,61 @@
+'use server';
+
+import { authenticateCustomer } from '@/lib/vendure';
+import { revalidateTag } from 'next/cache';
+import { AUTH_COOKIE_KEY, TAGS } from '@/lib/constants';
+import { cookies } from 'next/headers';
+
+export type SignInState =
+ | {
+ type: 'success';
+ id: string;
+ }
+ | {
+ type: 'error';
+ message: string;
+ }
+ | null;
+
+export async function signIn(
+ prevState: SignInState | null,
+ formData: FormData
+): Promise {
+ const username = formData.get('username');
+ const password = formData.get('password');
+
+ if (!username || !password) {
+ return {
+ type: 'error',
+ message: 'Missing username or password'
+ };
+ }
+
+ try {
+ const res = await authenticateCustomer(username.toString(), password.toString());
+ revalidateTag(TAGS.customer);
+
+ if (res.__typename === 'CurrentUser') {
+ return {
+ type: 'success',
+ id: res.id
+ };
+ }
+
+ if (res.__typename === 'InvalidCredentialsError' || res.__typename === 'NotVerifiedError') {
+ return {
+ type: 'error',
+ message: res.message
+ };
+ }
+
+ return {
+ type: 'error',
+ message: 'Error signing in'
+ };
+ } catch (e) {
+ return {
+ type: 'error',
+ message: 'Error signing in'
+ };
+ }
+}
diff --git a/components/account/open-sign-in.tsx b/components/account/open-sign-in.tsx
new file mode 100644
index 0000000000..de7dd9a1e3
--- /dev/null
+++ b/components/account/open-sign-in.tsx
@@ -0,0 +1,14 @@
+import { UserIcon } from '@heroicons/react/24/outline';
+import clsx from 'clsx';
+import Link from 'next/link';
+
+export default function OpenSignIn({ className }: { className?: string }) {
+ return (
+
+
+
+ );
+}
diff --git a/components/account/sign-in-form.tsx b/components/account/sign-in-form.tsx
new file mode 100644
index 0000000000..dd6cf64518
--- /dev/null
+++ b/components/account/sign-in-form.tsx
@@ -0,0 +1,94 @@
+'use client';
+
+import { z } from 'zod';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage
+} from '@/ui-components/ui/form';
+import { Input } from '@/ui-components/ui/input';
+import { LoaderButton } from '@/components/loader-button';
+import { signIn, SignInState } from '@/components/account/actions';
+import { useActionState, useEffect, useTransition } from 'react';
+import { useToast } from '@/ui-components/hooks/use-toast';
+
+const formSchema = z.object({
+ username: z.string().min(3),
+ password: z.string().min(6)
+});
+
+type FormSchema = z.infer;
+
+export function SignInForm() {
+ const { toast } = useToast();
+ const form = useForm({
+ mode: 'all',
+ resolver: zodResolver(formSchema)
+ });
+ const [state, formAction] = useActionState(signIn, null);
+ const [pending, startTransaction] = useTransition();
+
+ useEffect(() => {
+ if (state?.type === 'error') {
+ toast({
+ variant: 'destructive',
+ title: 'Error',
+ description: state.message
+ });
+ } else if (state?.type === 'success') {
+ toast({
+ variant: 'default',
+ title: 'Success',
+ description: 'Welcome back!'
+ });
+ }
+ }, [state]);
+
+ return (
+
+
+ );
+}
diff --git a/components/cart/add-to-cart.tsx b/components/cart/add-to-cart.tsx
index 4ac63e9217..b194d8d1a6 100644
--- a/components/cart/add-to-cart.tsx
+++ b/components/cart/add-to-cart.tsx
@@ -26,8 +26,7 @@ function SubmitButton({
);
}
-
- console.log(selectedVariantId);
+
if (!selectedVariantId) {
return (