Trying to add avatar upload. Project works rn so wanna push
This commit is contained in:
2
convex/_generated/api.d.ts
vendored
2
convex/_generated/api.d.ts
vendored
@@ -15,6 +15,7 @@ import type {
|
|||||||
} from "convex/server";
|
} from "convex/server";
|
||||||
import type * as CustomPassword from "../CustomPassword.js";
|
import type * as CustomPassword from "../CustomPassword.js";
|
||||||
import type * as auth from "../auth.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 http from "../http.js";
|
||||||
import type * as myFunctions from "../myFunctions.js";
|
import type * as myFunctions from "../myFunctions.js";
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ import type * as myFunctions from "../myFunctions.js";
|
|||||||
declare const fullApi: ApiFromModules<{
|
declare const fullApi: ApiFromModules<{
|
||||||
CustomPassword: typeof CustomPassword;
|
CustomPassword: typeof CustomPassword;
|
||||||
auth: typeof auth;
|
auth: typeof auth;
|
||||||
|
files: typeof files;
|
||||||
http: typeof http;
|
http: typeof http;
|
||||||
myFunctions: typeof myFunctions;
|
myFunctions: typeof myFunctions;
|
||||||
}>;
|
}>;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
|
import { ConvexError, v } from 'convex/values';
|
||||||
import { convexAuth, getAuthUserId } from '@convex-dev/auth/server';
|
import { convexAuth, getAuthUserId } from '@convex-dev/auth/server';
|
||||||
import { query } from './_generated/server';
|
import { mutation, query } from './_generated/server';
|
||||||
import Password from './CustomPassword';
|
import Password from './CustomPassword';
|
||||||
|
|
||||||
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
|
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
|
||||||
@@ -10,7 +11,7 @@ export const getUser = query(async (ctx) => {
|
|||||||
const userId = await getAuthUserId(ctx);
|
const userId = await getAuthUserId(ctx);
|
||||||
if (!userId) return null;
|
if (!userId) return null;
|
||||||
const user = await ctx.db.get(userId);
|
const user = await ctx.db.get(userId);
|
||||||
if (!user) return null;
|
if (!user) throw new ConvexError('User not found.');
|
||||||
return {
|
return {
|
||||||
id: user._id,
|
id: user._id,
|
||||||
email: user.email ?? null,
|
email: user.email ?? null,
|
||||||
@@ -18,3 +19,15 @@ export const getUser = query(async (ctx) => {
|
|||||||
image: user.image ?? null,
|
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
13
convex/files.ts
Normal 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);
|
||||||
|
},
|
||||||
|
});
|
@@ -1,4 +1,16 @@
|
|||||||
const Profile = () => {
|
'use server';
|
||||||
return <div></div>;
|
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;
|
export default Profile;
|
||||||
|
69
src/components/layout/profile/avatar-upload.tsx
Normal file
69
src/components/layout/profile/avatar-upload.tsx
Normal 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 };
|
28
src/components/layout/profile/header.tsx
Normal file
28
src/components/layout/profile/header.tsx
Normal 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 & how it appears to others.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ProfileHeader };
|
2
src/components/layout/profile/index.tsx
Normal file
2
src/components/layout/profile/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { AvatarUpload } from './avatar-upload';
|
||||||
|
export { ProfileHeader } from './header';
|
@@ -6,7 +6,7 @@ import {
|
|||||||
import { banSuspiciousIPs } from '@/lib/middleware/ban-suspicious-ips';
|
import { banSuspiciousIPs } from '@/lib/middleware/ban-suspicious-ips';
|
||||||
|
|
||||||
const isSignInPage = createRouteMatcher(['/signin']);
|
const isSignInPage = createRouteMatcher(['/signin']);
|
||||||
const isProtectedRoute = createRouteMatcher(['/', '/server']);
|
const isProtectedRoute = createRouteMatcher(['/', '/profile']);
|
||||||
|
|
||||||
export default convexAuthNextjsMiddleware(async (request, { convexAuth }) => {
|
export default convexAuthNextjsMiddleware(async (request, { convexAuth }) => {
|
||||||
const banResponse = banSuspiciousIPs(request);
|
const banResponse = banSuspiciousIPs(request);
|
||||||
|
Reference in New Issue
Block a user