Trying to add avatar upload. Project works rn so wanna push

This commit is contained in:
2025-09-03 10:52:10 -05:00
parent 35215d34a1
commit 6febfa05d4
8 changed files with 144 additions and 5 deletions

View File

@@ -15,6 +15,7 @@ import type {
} from "convex/server";
import type * as CustomPassword from "../CustomPassword.js";
import type * as auth from "../auth.js";
import type * as files from "../files.js";
import type * as http from "../http.js";
import type * as myFunctions from "../myFunctions.js";
@@ -29,6 +30,7 @@ import type * as myFunctions from "../myFunctions.js";
declare const fullApi: ApiFromModules<{
CustomPassword: typeof CustomPassword;
auth: typeof auth;
files: typeof files;
http: typeof http;
myFunctions: typeof myFunctions;
}>;

View File

@@ -1,5 +1,6 @@
import { ConvexError, v } from 'convex/values';
import { convexAuth, getAuthUserId } from '@convex-dev/auth/server';
import { query } from './_generated/server';
import { mutation, query } from './_generated/server';
import Password from './CustomPassword';
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
@@ -10,7 +11,7 @@ export const getUser = query(async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) return null;
const user = await ctx.db.get(userId);
if (!user) return null;
if (!user) throw new ConvexError('User not found.');
return {
id: user._id,
email: user.email ?? null,
@@ -18,3 +19,15 @@ export const getUser = query(async (ctx) => {
image: user.image ?? null,
};
});
export const updateUserImage = mutation({
args: {
storageId: v.id('_storage')
},
handler: async (ctx, {storageId}) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new ConvexError('Not authenticated.');
await ctx.db.patch(userId, { image: storageId });
return { success: true };
},
});

13
convex/files.ts Normal file
View File

@@ -0,0 +1,13 @@
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
export const generateUploadUrl = mutation(async (ctx) => {
return await ctx.storage.generateUploadUrl();
});
export const getImageUrl = query({
args: { storageId: v.id("_storage") },
handler: async (ctx, { storageId }) => {
return await ctx.storage.getUrl(storageId);
},
});

View File

@@ -1,4 +1,16 @@
const Profile = () => {
return <div></div>;
'use server';
import { preloadQuery } from "convex/nextjs";
import { api } from "~/convex/_generated/api";
import { AvatarUpload, ProfileHeader } from "@/components/layout/profile";
import { Card } from "@/components/ui";
const Profile = async () => {
const preloadedUser = await preloadQuery(api.auth.getUser);
return (
<Card className='max-w-2xl min-w-xs sm:min-w-md mx-auto mb-8'>
<ProfileHeader preloadedUser={preloadedUser} />
<AvatarUpload preloadedUser={preloadedUser} />
</Card>
);
};
export default Profile;

View File

@@ -0,0 +1,69 @@
'use client';
import { type Preloaded, usePreloadedQuery } from 'convex/react';
import { type api } from '~/convex/_generated/api';
import {
BasedAvatar,
CardContent,
} from '@/components/ui';
import { Loader2, Pencil, Upload } from 'lucide-react';
type AvatarUploadProps = {
preloadedUser: Preloaded<typeof api.auth.getUser>;
}
const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
const user = usePreloadedQuery(preloadedUser);
return (
<CardContent>
<div className='flex flex-col items-center'>
<div
className='relative group cursor-pointer mb-4'
onClick={() => {}}
>
<BasedAvatar
src={user?.image}
fullName={user?.name}
className='h-32 w-32'
fallbackProps={{ className: 'text-4xl font-semibold' }}
userIconProps={{ size: 100 }}
/>
<div
className='absolute inset-0 rounded-full bg-black/0 group-hover:bg-black/50
transition-all flex items-center justify-center'
>
<Upload
className='text-white opacity-0 group-hover:opacity-100
transition-opacity'
size={24}
/>
</div>
<div className='absolute inset-1 transition-all flex items-end justify-end'>
<Pencil
className='text-white opacity-100 group-hover:opacity-0
transition-opacity'
size={24}
/>
</div>
</div>
{
//<input
//ref={fileInputRef}
//type='file'
//accept='image/*'
//className='hidden'
//onChange={handleFileChange}
//disabled={isUploading}
///>
//{isUploading && (
//<div className='flex items-center text-sm text-gray-500 mt-2'>
//<Loader2 className='h-4 w-4 mr-2 animate-spin' />
//Uploading...
//</div>
//)}
}
</div>
</CardContent>
);
};
export { AvatarUpload };

View File

@@ -0,0 +1,28 @@
'use client';
import { type Preloaded, usePreloadedQuery } from 'convex/react';
import { type api } from '~/convex/_generated/api';
import {
CardHeader,
CardTitle,
CardDescription,
} from '@/components/ui';
type ProfileCardProps = {
preloadedUser: Preloaded<typeof api.auth.getUser>;
}
const ProfileHeader = ({ preloadedUser }: ProfileCardProps) => {
const user = usePreloadedQuery(preloadedUser);
return (
<CardHeader className='pb-2'>
<CardTitle className='text-2xl'>
{user?.name ?? user?.email ?? 'Your Profile'}
</CardTitle>
<CardDescription>
Manage your personal information &amp; how it appears to others.
</CardDescription>
</CardHeader>
);
};
export { ProfileHeader };

View File

@@ -0,0 +1,2 @@
export { AvatarUpload } from './avatar-upload';
export { ProfileHeader } from './header';

View File

@@ -6,7 +6,7 @@ import {
import { banSuspiciousIPs } from '@/lib/middleware/ban-suspicious-ips';
const isSignInPage = createRouteMatcher(['/signin']);
const isProtectedRoute = createRouteMatcher(['/', '/server']);
const isProtectedRoute = createRouteMatcher(['/', '/profile']);
export default convexAuthNextjsMiddleware(async (request, { convexAuth }) => {
const banResponse = banSuspiciousIPs(request);