cleaning shit up
This commit is contained in:
@ -73,7 +73,7 @@
|
|||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-hook-form": "^7.58.1",
|
"react-hook-form": "^7.58.1",
|
||||||
"react-resizable-panels": "^3.0.3",
|
"react-resizable-panels": "^3.0.3",
|
||||||
"recharts": "^3.0.0",
|
"recharts": "^3.0.1",
|
||||||
"sonner": "^2.0.5",
|
"sonner": "^2.0.5",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
@ -81,7 +81,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@tailwindcss/postcss": "^4.1.10",
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
"@types/cors": "^2.8.19",
|
"@types/cors": "^2.8.19",
|
||||||
"@types/express": "^5.0.3",
|
"@types/express": "^5.0.3",
|
||||||
"@types/node": "^20.19.1",
|
"@types/node": "^20.19.1",
|
||||||
@ -96,7 +96,7 @@
|
|||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.6.1",
|
"prettier": "^3.6.1",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.13",
|
"prettier-plugin-tailwindcss": "^0.6.13",
|
||||||
"tailwindcss": "^4.1.10",
|
"tailwindcss": "^4.1.11",
|
||||||
"tw-animate-css": "^1.3.4",
|
"tw-animate-css": "^1.3.4",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.35.0"
|
"typescript-eslint": "^8.35.0"
|
||||||
|
19
public/icons/auth/apple.svg
Normal file
19
public/icons/auth/apple.svg
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="-1.5 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
|
<title>apple [#173]</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
|
||||||
|
</defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Dribbble-Light-Preview" transform="translate(-102.000000, -7439.000000)" fill="#000000">
|
||||||
|
<g id="icons" transform="translate(56.000000, 160.000000)">
|
||||||
|
<path d="M57.5708873,7282.19296 C58.2999598,7281.34797 58.7914012,7280.17098 58.6569121,7279 C57.6062792,7279.04 56.3352055,7279.67099 55.5818643,7280.51498 C54.905374,7281.26397 54.3148354,7282.46095 54.4735932,7283.60894 C55.6455696,7283.69593 56.8418148,7283.03894 57.5708873,7282.19296 M60.1989864,7289.62485 C60.2283111,7292.65181 62.9696641,7293.65879 63,7293.67179 C62.9777537,7293.74279 62.562152,7295.10677 61.5560117,7296.51675 C60.6853718,7297.73474 59.7823735,7298.94772 58.3596204,7298.97372 C56.9621472,7298.99872 56.5121648,7298.17973 54.9134635,7298.17973 C53.3157735,7298.17973 52.8162425,7298.94772 51.4935978,7298.99872 C50.1203933,7299.04772 49.0738052,7297.68074 48.197098,7296.46676 C46.4032359,7293.98379 45.0330649,7289.44985 46.8734421,7286.3899 C47.7875635,7284.87092 49.4206455,7283.90793 51.1942837,7283.88393 C52.5422083,7283.85893 53.8153044,7284.75292 54.6394294,7284.75292 C55.4635543,7284.75292 57.0106846,7283.67793 58.6366882,7283.83593 C59.3172232,7283.86293 61.2283842,7284.09893 62.4549652,7285.8199 C62.355868,7285.8789 60.1747177,7287.09489 60.1989864,7289.62485" id="apple-[#173]">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
2
public/icons/auth/microsoft.svg
Normal file
2
public/icons/auth/microsoft.svg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"><path fill="#F35325" d="M1 1h6.5v6.5H1V1z"/><path fill="#81BC06" d="M8.5 1H15v6.5H8.5V1z"/><path fill="#05A6F0" d="M1 8.5h6.5V15H1V8.5z"/><path fill="#FFBA08" d="M8.5 8.5H15V15H8.5V8.5z"/></svg>
|
After Width: | Height: | Size: 414 B |
3
src/components/default/auth/buttons/client/index.tsx
Normal file
3
src/components/default/auth/buttons/client/index.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { SignInWithApple } from './sign-in-with-apple';
|
||||||
|
export { SignInWithMicrosoft } from './sign-in-with-microsoft';
|
||||||
|
export { SignInButton } from './sign-in';
|
@ -1,5 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { signInWithApple, getProfile, updateProfile } from '@/lib/queries';
|
import { signInWithApple } from '@/lib/queries';
|
||||||
import { StatusMessage, SubmitButton } from '@/components/default/forms';
|
import { StatusMessage, SubmitButton } from '@/components/default/forms';
|
||||||
import { useAuth } from '@/lib/hooks/context';
|
import { useAuth } from '@/lib/hooks/context';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@ -85,7 +85,7 @@ export const SignInWithApple = ({
|
|||||||
width={imageProps.width}
|
width={imageProps.width}
|
||||||
height={imageProps.height}
|
height={imageProps.height}
|
||||||
/>
|
/>
|
||||||
<p className={textProps.className}>Sign In with Apple</p>
|
<p className={textProps.className} {...textProps}>Sign In with Apple</p>
|
||||||
</div>
|
</div>
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
{statusMessage && <StatusMessage message={{ error: statusMessage }} />}
|
{statusMessage && <StatusMessage message={{ error: statusMessage }} />}
|
@ -0,0 +1,94 @@
|
|||||||
|
'use client';
|
||||||
|
import { signInWithMicrosoft } from '@/lib/queries';
|
||||||
|
import { StatusMessage, SubmitButton } from '@/components/default/forms';
|
||||||
|
import { useAuth } from '@/lib/hooks/context';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { type buttonVariants } from '@/components/ui';
|
||||||
|
import { type ComponentProps } from 'react';
|
||||||
|
import { type VariantProps } from 'class-variance-authority';
|
||||||
|
import { useSupabaseClient } from '@/utils/supabase';
|
||||||
|
|
||||||
|
type ButtonProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants>;
|
||||||
|
type ImageProps = {
|
||||||
|
src: string;
|
||||||
|
alt: string;
|
||||||
|
className?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
}
|
||||||
|
type FormProps = ComponentProps<'form'>;
|
||||||
|
type TextProps = ComponentProps<'p'>;
|
||||||
|
type SignInWithMicrosoftProps = {
|
||||||
|
buttonProps?: ButtonProps;
|
||||||
|
imageProps?: ImageProps;
|
||||||
|
formProps?: FormProps;
|
||||||
|
textProps?: TextProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SignInWithMicrosoft = ({
|
||||||
|
buttonProps = {
|
||||||
|
className: 'w-full cursor-pointer',
|
||||||
|
type: 'submit',
|
||||||
|
},
|
||||||
|
imageProps = {
|
||||||
|
src: '/icons/auth/microsoft.svg',
|
||||||
|
alt: 'Apple',
|
||||||
|
className: '',
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
},
|
||||||
|
formProps = {
|
||||||
|
className: 'my-4',
|
||||||
|
},
|
||||||
|
textProps = {
|
||||||
|
className: 'text-[1.0rem]',
|
||||||
|
},
|
||||||
|
} : SignInWithMicrosoftProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { loading, refreshUser } = useAuth();
|
||||||
|
const [statusMessage, setStatusMessage] = useState('');
|
||||||
|
const [ isLoading, setIsLoading ] = useState(false);
|
||||||
|
const supabase = useSupabaseClient();
|
||||||
|
|
||||||
|
const handleSignInWithMicrosoft = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
if (!supabase) throw new Error('Supabase client not found');
|
||||||
|
setStatusMessage('');
|
||||||
|
setIsLoading(true);
|
||||||
|
const result = await signInWithMicrosoft(supabase);
|
||||||
|
if (result.data.url) window.location.href = result.data.url;
|
||||||
|
else setStatusMessage(`There was a problem signing in with Apple!`);
|
||||||
|
} catch (error) {
|
||||||
|
setStatusMessage(`Error signing in: ${error as string}`);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
await refreshUser();
|
||||||
|
router.push('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSignInWithMicrosoft} {...formProps}>
|
||||||
|
<SubmitButton
|
||||||
|
disabled={isLoading || loading}
|
||||||
|
pendingText='Signing in...'
|
||||||
|
{...buttonProps}
|
||||||
|
>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<Image
|
||||||
|
src={imageProps.src}
|
||||||
|
alt={imageProps.alt}
|
||||||
|
className={imageProps.className}
|
||||||
|
width={imageProps.width}
|
||||||
|
height={imageProps.height}
|
||||||
|
/>
|
||||||
|
<p className={textProps.className} {...textProps}>Sign In with Apple</p>
|
||||||
|
</div>
|
||||||
|
</SubmitButton>
|
||||||
|
{statusMessage && <StatusMessage message={{ error: statusMessage }} />}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
126
src/components/default/auth/cards/client/sign-in.tsx
Executable file
126
src/components/default/auth/cards/client/sign-in.tsx
Executable file
@ -0,0 +1,126 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { signIn, signUp } from '@/lib/queries';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useAuth } from '@/lib/hooks/context';
|
||||||
|
import { useSupabaseClient } from '@/utils/supabase';
|
||||||
|
import { StatusMessage, SubmitButton } from '@/components/default/forms';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
Input,
|
||||||
|
Separator,
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from '@/components/ui';
|
||||||
|
import {
|
||||||
|
SignInWithApple,
|
||||||
|
SignInWithMicrosoft
|
||||||
|
} from '@/components/default/auth/buttons/client';
|
||||||
|
|
||||||
|
const signInFormSchema = z.object({
|
||||||
|
email: z.string().email({
|
||||||
|
message: 'Please enter a valid email address.'
|
||||||
|
}),
|
||||||
|
password: z.string().min(8, {
|
||||||
|
message: 'Password must be at least 8 characters.'
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const signUpformSchema = z
|
||||||
|
.object({
|
||||||
|
name: z.string().min(2, {
|
||||||
|
message: 'Name must be at least 2 characters.',
|
||||||
|
}),
|
||||||
|
email: z.string().email({
|
||||||
|
message: 'Please enter a valid email address.',
|
||||||
|
}),
|
||||||
|
password: z.string().min(8, {
|
||||||
|
message: 'Password must be at least 8 characters.',
|
||||||
|
}),
|
||||||
|
confirmPassword: z.string().min(8, {
|
||||||
|
message: 'Password must be at least 8 characters.',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.refine((data) => data.password === data.confirmPassword, {
|
||||||
|
message: 'Passwords do not match!',
|
||||||
|
path: ['confirmPassword'],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SignInCard = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { isAuthenticated, loading, refreshUser } = useAuth();
|
||||||
|
const [statusMessage, setStatusMessage] = useState('');
|
||||||
|
const supabase = useSupabaseClient();
|
||||||
|
|
||||||
|
const signInForm = useForm<z.infer<typeof signInFormSchema>>({
|
||||||
|
resolver: zodResolver(signInFormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const signUpForm = useForm<z.infer<typeof signUpformSchema>>({
|
||||||
|
resolver: zodResolver(signUpformSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAuthenticated) router.push('/');
|
||||||
|
}, [isAuthenticated, router]);
|
||||||
|
|
||||||
|
const handleSignIn = async (values: z.infer<typeof signInFormSchema>) => {
|
||||||
|
try {
|
||||||
|
setStatusMessage('');
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('email', values.email);
|
||||||
|
formData.append('password', values.password);
|
||||||
|
if (!supabase) throw new Error('Supabase client not found');
|
||||||
|
const result = await signIn(supabase, formData);
|
||||||
|
if (result.error) throw new Error(result.error.message);
|
||||||
|
else if (result.data) {
|
||||||
|
await refreshUser();
|
||||||
|
signInForm.reset();
|
||||||
|
router.push('');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setStatusMessage(`Error signing in: ${error as string}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSignUp = async (values: z.infer<typeof signUpFormSchema>) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
@ -1,2 +1,2 @@
|
|||||||
export { StatusMessage } from './StatusMessage';
|
export { StatusMessage } from './status-message';
|
||||||
export { SubmitButton } from './SubmitButton';
|
export { SubmitButton } from './submit-button';
|
||||||
|
Reference in New Issue
Block a user