Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add socials #540

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,27 @@ model User {
email String @unique
emailVerified DateTime?

skills String[]
experience Experience[]
project Project[]
resume String?

skills String[]
experience Experience[]
project Project[]
resume String?
socials Socials[]

oauthProvider OauthProvider? // Tracks OAuth provider (e.g., 'google')
oauthId String?

blockedByAdmin DateTime?
onBoard Boolean @default(false)
}

model Socials {
id String @id @default(cuid())
userId String
platform String
link String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}

enum OauthProvider {
GOOGLE
}
Expand Down
81 changes: 81 additions & 0 deletions src/actions/user.profile.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
expFormSchemaType,
projectSchemaType,
UserProfileSchemaType,
userSocialSchemaType,
} from '@/lib/validators/user.profile.validator';
import bcryptjs from 'bcryptjs';
import { authOptions } from '@/lib/authOptions';
Expand Down Expand Up @@ -317,3 +318,83 @@ export const getUserDetails = async () => {
return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
}
};

export const getUserSocials = async () => {
const auth = await getServerSession(authOptions);
if (!auth || !auth?.user?.id)
throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
try {
const res = await prisma.user.findFirst({
where: {
id: auth.user.id,
},
select: {
socials: true,
},
});
return new SuccessResponse(
'Socials SuccessFully Fetched',
200,
res
).serialize();
} catch (_error) {
return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
}
};

export const addUserSocials = withServerActionAsyncCatcher<
userSocialSchemaType,
ServerActionReturnType
>(async (data) => {
const auth = await getServerSession(authOptions);

if (!auth || !auth?.user?.id)
throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');

try {
await prisma.socials.create({
data: {
...data,
userId: auth.user.id,
},
});
return new SuccessResponse('Project updated successfully', 200).serialize();
} catch (_error) {
return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
}
});

export const deleteUserSocials = withServerActionAsyncCatcher<
{ socialId: string },
ServerActionReturnType
>(async ({ socialId }) => {
const auth = await getServerSession(authOptions);

if (!auth || !auth?.user?.id)
throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
await prisma.socials.delete({
where: {
id: socialId,
},
});
return new SuccessResponse('Socials deleted successfully', 200).serialize();
});

export const updateUserSocials = withServerActionAsyncCatcher<
{ socialId: string; data: userSocialSchemaType },
ServerActionReturnType
>(async ({ socialId, data }) => {
const auth = await getServerSession(authOptions);

if (!auth || !auth?.user?.id)
throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
await prisma.socials.update({
where: {
id: socialId,
},
data: {
...data,
},
});
return new SuccessResponse('Socials updated successfully', 200).serialize();
});
3 changes: 3 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,6 @@ html {
html {
scroll-behavior: smooth;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
2 changes: 1 addition & 1 deletion src/app/profile/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div className="container flex max-md:flex-col md:gap-5 w-full relative">
<Sidebar />
<div className="flex px-2 w-full overflow-y-auto md:max-h-[73vh] lg:h-full md:border md:rounded-xl md:pt-6 ">
<div className="flex px-2 w-full overflow-y-auto md:max-h-[73vh] lg:h-full md:border md:rounded-xl md:pt-6 no-scrollbar">
{children}
</div>
</div>
Expand Down
204 changes: 204 additions & 0 deletions src/components/profile/AddSocials.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
'use client';

import { useEffect, useTransition } from 'react';

import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';

import APP_PATHS from '@/config/path.config';
import { Input } from '@/components/ui/input';
import { Button } from '../ui/button';

import {
userSocialSchema,
userSocialSchemaType,
} from '@/lib/validators/user.profile.validator';

import { useToast } from '../ui/use-toast';

import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '../ui/form';
import {
addUserSocials,
deleteUserSocials,
} from '@/actions/user.profile.actions';

import Loader from '../loader';
import { Trash } from 'lucide-react';
type Props = {
socials?: userSocialSchemaType;
};
export const AddSocials = ({ socials }: Props) => {
const router = useRouter();
const session = useSession();
const { toast } = useToast();
const [isPending, startTransition] = useTransition();

const form = useForm<userSocialSchemaType>({
resolver: zodResolver(userSocialSchema),
defaultValues: {
platform: socials?.platform || '',
link: socials?.link || '',
},
});

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
form.setValue(e.target.name as keyof userSocialSchemaType, e.target.value);
};

const handleFormSubmit = async (data: userSocialSchemaType) => {
try {
startTransition(() => {
addUserSocials(data).then((res) => {
if (res.status) {
toast({
title: 'Socials added successfully',
variant: 'success',
});
form.reset();
} else {
toast({
title: res.message || 'Internal server error',
variant: 'destructive',
});
}
});
});
} catch (error: any) {
toast({
title: error?.message || 'Internal server error',
variant: 'destructive',
});
}
};

const delteteSocial = async (id: string) => {
try {
startTransition(() => {
deleteUserSocials({ socialId: id }).then((res) => {
if (res.status) {
toast({
title: 'Socials deleted successfully',
variant: 'success',
});
window.location.reload();
} else {
toast({
title: res.message || 'Internal server error',
variant: 'destructive',
});
}
});
});
} catch (error: any) {
toast({
title: error?.message || 'Internal server error',
variant: 'destructive',
});
}
};
useEffect(() => {
if (session.status !== 'loading' && session.status === 'unauthenticated')
router.push(`${APP_PATHS.SIGNIN}?redirectTo=/profile/edit`);
});

return (
<Form {...form}>
<form
className="flex flex-col gap-3 w-full"
onSubmit={form.handleSubmit(handleFormSubmit)}
>
<div
className={`flex ${socials && 'lg:flex-row'} md:flex-col w-full gap-3 max-md:items-end lg:items-end max-sm:flex-col`}
>
<div
className={`flex ${socials && 'lg:flex-row'} md:w-full gap-3 w-full max-md:items-end`}
>
<div
className={`${socials ? 'md:w-full lg:w-1/2 sm:w-1/2' : 'w-1/2'}`}
>
<FormField
control={form.control}
name="platform"
render={({ field }) => (
<FormItem>
<FormLabel>Social Platform Name</FormLabel>
<FormControl>
<Input
{...field}
disabled={isPending}
onChange={handleInputChange}
className="rounded focus-visible:ring-0 focus:outline-none focus:border-slate-500"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div
className={`${socials ? 'md:w-full lg:w-1/2 sm:w-1/2' : 'w-1/2'}`}
>
<FormField
control={form.control}
name="link"
render={({ field }) => (
<FormItem>
<FormLabel>Social URL</FormLabel>
<FormControl>
<Input
{...field}
disabled={isPending}
onChange={handleInputChange}
className="rounded focus-visible:ring-0 focus:outline-none focus:border-slate-500"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
<div className="flex justify-end">
{socials ? (
<div className="flex gap-3">
<Button
disabled={isPending}
className="bg-slate-950 text-white dark:text-slate-950 dark:bg-white rounded-md py-2 px-4"
>
{isPending ? <Loader /> : 'Save'}
</Button>
<Button
variant={'ghost'}
disabled={isPending}
onClick={() =>
socials && socials?.id && delteteSocial(socials.id)
}
className="flex items-center justify-center gap-3 text-xs text-red-400 bg-none border-none bg-transparent hover:bg-transparent"
>
<Trash className="w-4 h-4" />
Remove
</Button>
</div>
) : (
<Button
disabled={isPending}
className={`bg-slate-950 text-white dark:text-slate-950 dark:bg-white rounded-md py-2 px-4 ${!socials && 'md:w-56'} w-full`}
>
{isPending ? <Loader /> : 'Save'}
</Button>
)}
</div>
</div>
</form>
</Form>
);
};
Loading
Loading