diff --git a/bun.lockb b/bun.lockb index 331110f..c069b26 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index be95821..5723eeb 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", - "@sentry/nextjs": "^9.34.0", + "@sentry/nextjs": "^9.35.0", "@supabase-cache-helpers/postgrest-react-query": "^1.13.4", "@supabase/ssr": "^0.6.1", "@supabase/supabase-js": "^2.50.3", @@ -64,21 +64,21 @@ "import-in-the-middle": "^1.14.2", "input-otp": "^1.4.2", "lucide-react": "^0.522.0", - "next": "^15.3.4", + "next": "^15.3.5", "next-plausible": "^3.12.4", "next-themes": "^0.4.6", "postgres": "^3.4.7", "react": "^19.1.0", - "react-day-picker": "^9.7.0", + "react-day-picker": "^9.8.0", "react-dom": "^19.1.0", - "react-hook-form": "^7.59.0", + "react-hook-form": "^7.60.0", "react-icons": "^5.5.0", "react-resizable-panels": "^3.0.3", "recharts": "^3.0.2", "sonner": "^2.0.6", "tailwind-merge": "^3.3.1", "vaul": "^1.1.2", - "zod": "^3.25.71" + "zod": "^3.25.75" }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", @@ -90,7 +90,7 @@ "@types/react-dom": "^19.1.6", "drizzle-kit": "^0.30.6", "eslint": "^9.30.1", - "eslint-config-next": "^15.3.4", + "eslint-config-next": "^15.3.5", "eslint-config-prettier": "^10.1.5", "eslint-plugin-drizzle": "^0.2.3", "eslint-plugin-prettier": "^5.5.1", diff --git a/src/components/default/auth/buttons/client/sign-in-with-apple.tsx b/src/components/default/auth/buttons/client/sign-in-with-apple.tsx index 358db52..c43b96f 100644 --- a/src/components/default/auth/buttons/client/sign-in-with-apple.tsx +++ b/src/components/default/auth/buttons/client/sign-in-with-apple.tsx @@ -15,21 +15,15 @@ import { cn } from '@/lib/utils'; export type SignInWithAppleProps = { submitButtonProps?: SubmitButtonProps; - formClassName?: ComponentProps<'form'>['className']; - formProps?: Omit, 'className'>; - textClassName?: ComponentProps<'p'>['className']; - textProps?: Omit, 'className'>; - iconClassName?: ComponentProps<'svg'>['className']; - iconProps?: Omit, 'className'>; + formProps?: ComponentProps<'form'>; + textProps?: ComponentProps<'p'>; + iconProps?: ComponentProps<'svg'>; }; export const SignInWithApple = ({ submitButtonProps, - formClassName, formProps, - textClassName, textProps, - iconClassName, iconProps, } : SignInWithAppleProps) => { const router = useRouter(); @@ -58,19 +52,19 @@ export const SignInWithApple = ({ return (
- -

+ +

Sign In with Apple

diff --git a/src/components/default/auth/buttons/client/sign-in-with-microsoft.tsx b/src/components/default/auth/buttons/client/sign-in-with-microsoft.tsx index 370846f..2aafc43 100644 --- a/src/components/default/auth/buttons/client/sign-in-with-microsoft.tsx +++ b/src/components/default/auth/buttons/client/sign-in-with-microsoft.tsx @@ -15,21 +15,15 @@ import { cn } from '@/lib/utils'; export type SignInWithMicrosoftProps = { submitButtonProps?: SubmitButtonProps; - formClassName?: ComponentProps<'form'>['className']; - formProps?: Omit, 'className'>; - textClassName?: ComponentProps<'p'>['className']; - textProps?: Omit, 'className'>; - iconClassName?: ComponentProps<'svg'>['className']; - iconProps?: Omit, 'className'>; + formProps?: ComponentProps<'form'>; + textProps?: ComponentProps<'p'>; + iconProps?: ComponentProps<'svg'>; }; export const SignInWithMicrosoft = ({ submitButtonProps, - formClassName, formProps, - textClassName, textProps, - iconClassName, iconProps, } : SignInWithMicrosoftProps) => { const router = useRouter(); @@ -59,18 +53,18 @@ export const SignInWithMicrosoft = ({ return (
- -

+ +

Sign In with Microsoft

diff --git a/src/components/default/auth/buttons/client/sign-out.tsx b/src/components/default/auth/buttons/client/sign-out.tsx index 2ee273f..04dd2aa 100644 --- a/src/components/default/auth/buttons/client/sign-out.tsx +++ b/src/components/default/auth/buttons/client/sign-out.tsx @@ -34,13 +34,13 @@ export const SignOut = ({ Sign Out diff --git a/src/components/default/auth/buttons/server/sign-in-with-apple.tsx b/src/components/default/auth/buttons/server/sign-in-with-apple.tsx index 009537c..8fcb06c 100644 --- a/src/components/default/auth/buttons/server/sign-in-with-apple.tsx +++ b/src/components/default/auth/buttons/server/sign-in-with-apple.tsx @@ -11,21 +11,15 @@ import { cn } from '@/lib/utils'; export type SignInWithAppleProps = { submitButtonProps?: SubmitButtonProps; - formClassName?: ComponentProps<'form'>['className']; - formProps?: Omit, 'className'>; - textClassName?: ComponentProps<'p'>['className']; - textProps?: Omit, 'className'>; - iconClassName?: ComponentProps<'svg'>['className']; - iconProps?: Omit, 'className'>; + formProps?: ComponentProps<'form'>; + textProps?: ComponentProps<'p'>; + iconProps?: ComponentProps<'svg'>; }; export const SignInWithApple = async ({ submitButtonProps, - formClassName, formProps, - textClassName, textProps, - iconClassName, iconProps, } : SignInWithAppleProps) => { const supabase = await SupabaseServer(); @@ -45,17 +39,17 @@ export const SignInWithApple = async ({ return (
- -

+ +

Sign In with Apple

diff --git a/src/components/default/auth/buttons/server/sign-in-with-microsoft.tsx b/src/components/default/auth/buttons/server/sign-in-with-microsoft.tsx index cfc965a..a3a013e 100644 --- a/src/components/default/auth/buttons/server/sign-in-with-microsoft.tsx +++ b/src/components/default/auth/buttons/server/sign-in-with-microsoft.tsx @@ -11,21 +11,15 @@ import { cn } from '@/lib/utils'; export type SignInWithMicrosoftProps = { submitButtonProps?: SubmitButtonProps; - formClassName?: ComponentProps<'form'>['className']; - formProps?: Omit, 'className'>; - textClassName?: ComponentProps<'p'>['className']; - textProps?: Omit, 'className'>; - iconClassName?: ComponentProps<'svg'>['className']; - iconProps?: Omit, 'className'>; + formProps?: ComponentProps<'form'>; + textProps?: ComponentProps<'p'>; + iconProps?: ComponentProps<'svg'>; }; export const SignInWithMicrosoft = async ({ submitButtonProps, - formClassName, formProps, - textClassName, textProps, - iconClassName, iconProps, } : SignInWithMicrosoftProps) => { const supabase = await SupabaseServer(); @@ -45,17 +39,17 @@ export const SignInWithMicrosoft = async ({ return (
- -

+ +

Sign In with Microsoft

diff --git a/src/components/default/auth/cards/client/forgot-password.tsx b/src/components/default/auth/cards/client/forgot-password.tsx index 5775411..0f6f697 100644 --- a/src/components/default/auth/cards/client/forgot-password.tsx +++ b/src/components/default/auth/cards/client/forgot-password.tsx @@ -32,33 +32,21 @@ const forgotPasswordFormSchema = z.object({ }); type ForgotPasswordCardProps = { - cardClassName?: ComponentProps['className']; - cardProps?: Omit, 'className'>; - cardTitleClassName?: ComponentProps['className']; - cardTitleProps?: Omit, 'className'>; - cardDescriptionClassName?: ComponentProps['className']; - cardDescriptionProps?: Omit, 'className'>; - signUpLinkClassName?: ComponentProps['className']; - signUpLinkProps?: Omit, 'className' | 'href'>; - formClassName?: ComponentProps<'form'>['className']; - formProps?: Omit, 'className' | 'onSubmit'>; - formLabelClassName?: ComponentProps['className']; - formLabelProps?: Omit, 'className'>; + cardProps?: ComponentProps; + cardTitleProps?: ComponentProps; + cardDescriptionProps?: ComponentProps; + signUpLinkProps?: Omit, 'href'>; + formProps?: Omit, 'onSubmit'>; + formLabelProps?: ComponentProps; buttonProps?: ComponentProps; }; export const ForgotPasswordCard = ({ - cardClassName, cardProps, - cardTitleClassName, cardTitleProps, - cardDescriptionClassName, cardDescriptionProps, - signUpLinkClassName, signUpLinkProps, - formClassName, formProps, - formLabelClassName, formLabelProps, buttonProps = { pendingText: 'Sending Reset Link...', @@ -101,25 +89,25 @@ export const ForgotPasswordCard = ({ return ( Forgot Password Don't have an account?{' '} Sign up! @@ -128,9 +116,9 @@ export const ForgotPasswordCard = ({ ( Email diff --git a/src/components/default/auth/cards/client/sign-in.tsx b/src/components/default/auth/cards/client/sign-in.tsx index 57e0208..c2c24bd 100755 --- a/src/components/default/auth/cards/client/sign-in.tsx +++ b/src/components/default/auth/cards/client/sign-in.tsx @@ -64,20 +64,13 @@ const signUpFormSchema = z }); type SignInCardProps = { - containerClassName?: ComponentProps['className']; - containerProps?: Omit, 'className'>; - tabsClassName?: ComponentProps['className']; - tabsProps?: Omit, 'className'>; - tabsListClassName?: ComponentProps['className']; - tabsListProps?: Omit, 'className'>; - tabsTriggerClassName?: ComponentProps['className']; - tabsTriggerProps?: Omit, 'className' | 'value'>; - cardClassName?: ComponentProps['className']; - cardProps?: Omit, 'className'>; - formClassName?: ComponentProps<'form'>['className']; - formProps?: Omit, 'className' | 'onSubmit'>; - formLabelClassName?: ComponentProps['className']; - formLabelProps?: Omit, 'className'>; + containerProps?: ComponentProps; + tabsProps?: ComponentProps; + tabsListProps?: ComponentProps; + tabsTriggerProps?: Omit, 'value'>; + cardProps?: ComponentProps; + formProps?: Omit, 'onSubmit'>; + formLabelProps?: ComponentProps; submitButtonProps?: Omit, 'pendingText' | 'disabled'>; signInWithAppleProps?: SignInWithAppleProps; @@ -85,19 +78,12 @@ type SignInCardProps = { }; export const SignInCard = ({ - containerClassName, containerProps, - tabsClassName, tabsProps = { defaultValue: 'sign-in' }, - tabsListClassName, tabsListProps, - tabsTriggerClassName, tabsTriggerProps, - cardClassName, cardProps, - formClassName, formProps, - formLabelClassName, formLabelProps, submitButtonProps, signInWithAppleProps, @@ -165,52 +151,52 @@ export const SignInCard = ({ return ( Sign In Sign Up ( + {...formLabelProps} + className={cn('text-xl', formLabelProps?.className)} + > Email @@ -240,8 +227,8 @@ export const SignInCard = ({
Password @@ -274,11 +261,11 @@ export const SignInCard = ({ Sign In @@ -290,6 +277,7 @@ export const SignInCard = ({
@@ -345,8 +332,8 @@ export const SignInCard = ({ render={({ field }) => ( Name @@ -366,8 +353,8 @@ export const SignInCard = ({ render={({ field }) => ( Email @@ -387,8 +374,8 @@ export const SignInCard = ({ render={({ field }) => ( Password @@ -408,8 +395,8 @@ export const SignInCard = ({ render={({ field }) => ( Confirm Passsword @@ -435,11 +422,11 @@ export const SignInCard = ({ Sign Up @@ -451,6 +438,7 @@ export const SignInCard = ({ diff --git a/src/components/default/auth/forms/client/profile/avatar-upload.tsx b/src/components/default/auth/forms/client/profile/avatar-upload.tsx new file mode 100644 index 0000000..7a0be69 --- /dev/null +++ b/src/components/default/auth/forms/client/profile/avatar-upload.tsx @@ -0,0 +1,72 @@ +'use client'; +import { useFileUpload } from '@/lib/hooks'; +import { useAuth } from '@/lib/hooks/context'; +import { useSupabaseClient } from '@/utils/supabase'; +import { + BasedAvatar, + Card, + CardContent, +} from '@/components/ui'; +import { Loader2, Pencil, Upload } from 'lucide-react'; +import type { ComponentProps, ChangeEvent } from 'react'; +import { toast } from 'sonner'; + +type AvatarUploadProps = { + onAvatarUploaded: (path: string) => Promise; +}; + +export const AvatarUpload = ({ + onAvatarUploaded, +}: AvatarUploadProps) => { + const { profile, isAuthenticated } = useAuth(); + const { isUploading, fileInputRef, uploadAvatarMutation } = useFileUpload(); + const client = useSupabaseClient(); + + const handleAvatarClick = () => { + if (!isAuthenticated) { + toast.error('You must be logged in to upload an avatar!'); + return; + } + fileInputRef.current?.click(); + }; + + const handleFileChange = async (e: ChangeEvent) => { + try { + const file = e.target.files?.[0]; + if (!file) throw new Error('No file selected!'); + if (!client) throw new Error('Supabase client not found!'); + if (!isAuthenticated) throw new Error('User is not authenticated!'); + if (!file.type.startsWith('image/')) throw new Error('File is not an image!'); + if (file.size > 8 * 1024 * 1024) throw new Error('File is too large!'); + + const fileExt = file.name.split('.').pop(); + const avatarPath = profile?.avatar_url ?? profile?.id; + + const avatarUrl = await uploadAvatarMutation.mutateAsync({ + client, + file, + bucket: 'avatars', + resize: { + maxWidth: 500, + maxHeight: 500, + quality: 0.8, + }, + replace: avatarPath, + }); + if (avatarUrl) await onAvatarUploaded(avatarUrl); + + } catch (error) { + toast.error(`Error: ${error as string}`); + } + }; + + return ( + + +
+
+
+
+ ); + +}; diff --git a/src/components/default/auth/forms/profile/profile-form.tsx b/src/components/default/auth/forms/client/profile/profile-form.tsx similarity index 100% rename from src/components/default/auth/forms/profile/profile-form.tsx rename to src/components/default/auth/forms/client/profile/profile-form.tsx diff --git a/src/components/default/auth/forms/profile/reset-password-form.tsx b/src/components/default/auth/forms/client/profile/reset-password-form.tsx similarity index 100% rename from src/components/default/auth/forms/profile/reset-password-form.tsx rename to src/components/default/auth/forms/client/profile/reset-password-form.tsx diff --git a/src/components/default/auth/forms/profile/avatar-upload.tsx b/src/components/default/auth/forms/server/profile/avatar-upload.tsx similarity index 100% rename from src/components/default/auth/forms/profile/avatar-upload.tsx rename to src/components/default/auth/forms/server/profile/avatar-upload.tsx diff --git a/src/components/default/auth/forms/server/profile/profile-form.tsx b/src/components/default/auth/forms/server/profile/profile-form.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/default/auth/forms/server/profile/reset-password-form.tsx b/src/components/default/auth/forms/server/profile/reset-password-form.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/default/forms/status-message.tsx b/src/components/default/forms/status-message.tsx index 03da2dd..52cabf3 100644 --- a/src/components/default/forms/status-message.tsx +++ b/src/components/default/forms/status-message.tsx @@ -8,45 +8,47 @@ type Message = type StatusMessageProps = { message: Message; - containerClassName?: ComponentProps<'div'>['className']; - containerProps?: Omit, 'className'>; - textClassName?: ComponentProps<'div'>['className']; - textProps?: Omit, 'className'>; + containerProps?: ComponentProps<'div'>; + textProps?: ComponentProps<'div'>; }; export const StatusMessage = ({ message, - containerClassName, containerProps, - textClassName, textProps, }: StatusMessageProps) => { return (
{'success' in message && ( -
{message.success}
)} {'error' in message && ( -
+
{message.error}
)} {'message' in message && ( -
+
{message.message}
)} diff --git a/src/components/default/forms/submit-button.tsx b/src/components/default/forms/submit-button.tsx index 8cdd63f..7fdd83c 100644 --- a/src/components/default/forms/submit-button.tsx +++ b/src/components/default/forms/submit-button.tsx @@ -10,39 +10,35 @@ export type SubmitButtonProps = Omit< 'type' | 'aria-disabled' > & { pendingText?: string; - pendingTextClassName?: ComponentProps<'p'>['className']; - pendingTextProps?: Omit, 'className'>; - loaderClassName?: ComponentProps['className']; - loaderProps?: Omit, 'className'>; + pendingTextProps?: ComponentProps<'p'>; + loaderProps?: ComponentProps; }; export const SubmitButton = ({ children, className, pendingText = 'Submitting...', - pendingTextClassName, pendingTextProps, - loaderClassName, loaderProps, ...props }: SubmitButtonProps) => { const { pending } = useFormStatus(); return (