-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
407 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { redirect } from 'next/navigation'; | ||
|
||
import prismadb from '@/lib/prismadb'; | ||
import { currentUser } from '@/lib/authentication'; | ||
import ViewResume from '@/components/admin/view-resume'; | ||
import UploadPdfButton from '@/components/admin/upload-pdf-button'; | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardHeader, | ||
CardTitle | ||
} from '@/components/ui/card'; | ||
|
||
export default async function AboutPage() { | ||
const user = await currentUser(); | ||
|
||
if (!user || !user.id) { | ||
redirect('/auth/sign-in'); | ||
} | ||
|
||
const resume = await prismadb.resume.findFirst({ | ||
where: { | ||
userId: user.id | ||
} | ||
}); | ||
|
||
return ( | ||
<Card className='rounded-lg border-none'> | ||
<CardHeader className='mx-[1px] pb-9'> | ||
<CardTitle className='text-xl font-bold items-center flex justify-between'> | ||
Resume | ||
<UploadPdfButton resume={resume} /> | ||
</CardTitle> | ||
<CardDescription>Manage your resume pdf file.</CardDescription> | ||
</CardHeader> | ||
<CardContent> | ||
<ViewResume url={resume?.pdf ?? null} /> | ||
</CardContent> | ||
</Card> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { NextResponse } from 'next/server'; | ||
import { revalidatePath } from 'next/cache'; | ||
|
||
import prismadb from '@/lib/prismadb'; | ||
import { currentUser } from '@/lib/authentication'; | ||
|
||
export async function POST(req: Request) { | ||
try { | ||
const body = await req.json(); | ||
const { pdf } = body; | ||
|
||
const user = await currentUser(); | ||
|
||
if (!pdf) { | ||
return NextResponse.json( | ||
{ success: false, error: 'File is required.' }, | ||
{ status: 400 } | ||
); | ||
} | ||
|
||
if (!user || !user.id) { | ||
return NextResponse.json( | ||
{ success: false, error: 'Unauthenticated.' }, | ||
{ status: 401 } | ||
); | ||
} | ||
|
||
const currentResume = await prismadb.resume.findFirst({ | ||
where: { | ||
userId: user.id | ||
} | ||
}); | ||
|
||
if (currentResume) { | ||
const resume = await prismadb.resume.update({ | ||
where: { | ||
id: currentResume.id | ||
}, | ||
data: { | ||
} | ||
}); | ||
|
||
revalidatePath('/'); | ||
|
||
return NextResponse.json({ success: true, resume }); | ||
} else { | ||
const resume = await prismadb.resume.create({ | ||
data: { | ||
pdf, | ||
userId: user.id | ||
} | ||
}); | ||
|
||
revalidatePath('/'); | ||
|
||
return NextResponse.json({ success: true, resume }); | ||
} | ||
} catch (error: any) { | ||
console.log('[RESUME_POST]', error); | ||
return NextResponse.json( | ||
{ success: false, error: error.message }, | ||
{ status: 500 } | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
'use client'; | ||
|
||
import { useState } from 'react'; | ||
import { Upload } from 'lucide-react'; | ||
import { Resume } from '@prisma/client'; | ||
|
||
import { Button } from '@/components/ui/button'; | ||
import UploadPdfModal from '@/components/modals/upload-pdf-modal'; | ||
|
||
interface UploadPdfButtonProps { | ||
resume: Resume | null; | ||
} | ||
|
||
export default function UploadPdfButton({ resume }: UploadPdfButtonProps) { | ||
const [open, setOpen] = useState(false); | ||
|
||
return ( | ||
<> | ||
<Button size='sm' onClick={() => setOpen(true)}> | ||
<Upload className='mr-2 h-4 w-4' /> | ||
Upload PDF | ||
</Button> | ||
<UploadPdfModal | ||
isOpen={open} | ||
onClose={() => setOpen(false)} | ||
resume={resume} | ||
/> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
'use client'; | ||
|
||
import * as z from 'zod'; | ||
import axios from 'axios'; | ||
import { useState } from 'react'; | ||
import { Loader2 } from 'lucide-react'; | ||
import { Resume } from '@prisma/client'; | ||
import { useForm } from 'react-hook-form'; | ||
import { useRouter } from 'next/navigation'; | ||
import { zodResolver } from '@hookform/resolvers/zod'; | ||
|
||
import { Input } from '@/components/ui/input'; | ||
import { useEdgeStore } from '@/lib/edgestore'; | ||
import { Button } from '@/components/ui/button'; | ||
import { useToast } from '@/components/ui/use-toast'; | ||
import { | ||
Form, | ||
FormControl, | ||
FormItem, | ||
FormLabel, | ||
FormMessage | ||
} from '@/components/ui/form'; | ||
|
||
interface UploadPdfFormProps { | ||
onClose: () => void; | ||
resume: Resume | null; | ||
} | ||
|
||
const formSchema = z.object({ | ||
pdf: z.string() | ||
}); | ||
|
||
export default function UploadPdfForm({ onClose, resume }: UploadPdfFormProps) { | ||
const router = useRouter(); | ||
const { toast } = useToast(); | ||
const [loading, setLoading] = useState(false); | ||
const [fileError, setFileError] = useState(''); | ||
const [file, setFile] = useState<File | string | undefined>( | ||
resume?.pdf ?? undefined | ||
); | ||
|
||
const { edgestore } = useEdgeStore(); | ||
|
||
const form = useForm<z.infer<typeof formSchema>>({ | ||
resolver: zodResolver(formSchema), | ||
defaultValues: { | ||
pdf: resume?.pdf ?? '' | ||
} | ||
}); | ||
|
||
const onSubmit = async (values: z.infer<typeof formSchema>) => { | ||
try { | ||
setLoading(true); | ||
|
||
let pdfURL = resume?.pdf ?? ''; | ||
if (file && file instanceof File) { | ||
const res = await edgestore.publicFiles.upload({ | ||
file, | ||
options: { | ||
replaceTargetUrl: resume?.pdf ?? undefined | ||
} | ||
}); | ||
|
||
if (res.url) { | ||
pdfURL = res.url; | ||
} | ||
} | ||
|
||
if (pdfURL === '') { | ||
setFileError('Please select file.'); | ||
return; | ||
} | ||
|
||
setFileError(''); | ||
const newValues = { ...values, pdf: pdfURL }; | ||
|
||
const response = await axios.post('/api/resume', newValues); | ||
|
||
if (response.data.success) { | ||
onClose(); | ||
router.refresh(); | ||
|
||
toast({ | ||
variant: 'default', | ||
title: 'Success!', | ||
description: 'Data has been successfully saved.' | ||
}); | ||
} | ||
} catch (error) { | ||
console.log(error); | ||
|
||
toast({ | ||
variant: 'destructive', | ||
title: 'Uh oh! Something went wrong.', | ||
description: 'There was a problem with your request.' | ||
}); | ||
} finally { | ||
setLoading(false); | ||
} | ||
}; | ||
|
||
return ( | ||
<Form {...form}> | ||
<form onSubmit={form.handleSubmit(onSubmit)}> | ||
<div className='flex flex-col gap-4'> | ||
<FormItem> | ||
<FormLabel htmlFor='pdfFile'>PDF File</FormLabel> | ||
<FormControl> | ||
<Input | ||
id='pdfFile' | ||
type='file' | ||
disabled={loading} | ||
defaultValue={file instanceof File ? file.name : ''} | ||
onChange={(event) => { | ||
const selectedFile = event.target.files?.[0]; | ||
if (selectedFile) { | ||
if ( | ||
selectedFile.type !== 'application/pdf' && | ||
selectedFile.type !== 'application/x-pdf' | ||
) { | ||
setFileError('Invalid file type.'); | ||
return; | ||
} | ||
|
||
if (selectedFile.size > 1024 * 1024 * 2) { | ||
setFileError('File size is too large. Max size is 2MB.'); | ||
return; | ||
} | ||
|
||
setFileError(''); | ||
setFile(selectedFile); | ||
} | ||
}} | ||
/> | ||
</FormControl> | ||
<FormMessage>{fileError}</FormMessage> | ||
</FormItem> | ||
<div className='pt-3 space-x-2 flex items-center justify-end w-full'> | ||
<Button | ||
type='button' | ||
disabled={loading} | ||
variant='outline' | ||
onClick={onClose} | ||
> | ||
Cancel | ||
</Button> | ||
<Button disabled={loading} type='submit' variant='default'> | ||
{loading && ( | ||
<> | ||
<Loader2 className='animate-spin mr-2' size={18} /> | ||
Saving... | ||
</> | ||
)} | ||
{!loading && <p className='px-4'>Save</p>} | ||
</Button> | ||
</div> | ||
</div> | ||
</form> | ||
</Form> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
'use client'; | ||
|
||
interface ViewResumeProps { | ||
url: string | null; | ||
} | ||
|
||
export default function ViewResume({ url }: ViewResumeProps) { | ||
return ( | ||
<> | ||
{!url && ( | ||
<div className='w-full h-[500px] flex justify-center items-center p-9 border rounded-md'> | ||
<p className='text-center font-medium'>No resume found.</p> | ||
</div> | ||
)} | ||
{url && ( | ||
<> | ||
<object | ||
data={url} | ||
type='application/pdf' | ||
width='100%' | ||
height='100%' | ||
className='w-full h-[1000px] rounded-md' | ||
> | ||
<iframe | ||
src={url} | ||
width='100%' | ||
height='100%' | ||
className='w-full h-[1000px]' | ||
> | ||
Your browser does not support PDF. | ||
</iframe> | ||
</object> | ||
</> | ||
)} | ||
</> | ||
); | ||
} |
Oops, something went wrong.