-
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Dodanie formularza do aktualizacji danych bloga (#101)
* Add endpoint to update blog data * Code review * Create form for updating blogs data * Code review * Code review * Fix generic types * Fix * Code review Co-authored-by: Michał Miszczyszyn <mmiszy@users.noreply.github.com>
- Loading branch information
Showing
19 changed files
with
532 additions
and
106 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
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 |
---|---|---|
@@ -1,6 +1,25 @@ | ||
import Boom from '@hapi/boom'; | ||
|
||
import { logger } from './logger'; | ||
import type { PrismaError } from './prisma-errors'; | ||
|
||
export function isPrismaError(err: any): err is PrismaError { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
return Boolean(err && err.code) && /^P\d{4}$/.test(err.code); | ||
} | ||
|
||
export function handlePrismaError(err: unknown) { | ||
if (!isPrismaError(err)) { | ||
throw err; | ||
} | ||
|
||
switch (err.code) { | ||
case 'P2001': | ||
throw Boom.notFound(); | ||
case 'P2002': | ||
throw Boom.conflict(); | ||
default: | ||
logger.error(`Unhandled Prisma error: ${err.code}`); | ||
break; | ||
} | ||
} |
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 |
---|---|---|
@@ -1,34 +1,3 @@ | ||
import { signIn, useSession } from 'next-auth/client'; | ||
import { useEffect } from 'react'; | ||
|
||
import { LoadingScreen } from '../LoadingScreen/LoadingScreen'; | ||
|
||
import styles from './AdminPanel.module.css'; | ||
|
||
export const AdminPanel = () => { | ||
const [session, isLoading] = useSession(); | ||
|
||
useEffect(() => { | ||
if (!isLoading && !session) { | ||
void signIn(); | ||
} | ||
}, [session, isLoading]); | ||
|
||
if (isLoading) { | ||
return <LoadingScreen />; | ||
} | ||
|
||
if (session?.user.role === 'ADMIN') { | ||
return <section>admin panel</section>; // @to-do admin-panel | ||
} | ||
|
||
return ( | ||
<section className={styles.section}> | ||
<h2 className={styles.heading}>Brak uprawnień</h2> | ||
<p className={styles.p}> | ||
Nie masz odpowiednich uprawnień, żeby korzystać z tej podstrony. W celu weryfikacji | ||
skontaktuj się z administracją serwisu. | ||
</p> | ||
</section> | ||
); | ||
return <section>admin panel</section>; | ||
}; |
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,43 @@ | ||
import { signIn, useSession } from 'next-auth/client'; | ||
import { useEffect } from 'react'; | ||
|
||
import { LoadingScreen } from '../LoadingScreen/LoadingScreen'; | ||
|
||
import styles from './authGuard.module.css'; | ||
|
||
type Props = { | ||
readonly role?: 'admin'; | ||
}; | ||
|
||
export const AuthGuard: React.FC<Props> = ({ children, role }) => { | ||
const [session, isLoading] = useSession(); | ||
|
||
useEffect(() => { | ||
if (!isLoading && !session) { | ||
void signIn(); | ||
} | ||
}, [session, isLoading]); | ||
|
||
if (isLoading) { | ||
return <LoadingScreen />; | ||
} | ||
|
||
// Without role allow all authorized users | ||
if (!role && session) { | ||
return <>{children}</>; | ||
} | ||
|
||
if (role === 'admin' && session?.user.role === 'ADMIN') { | ||
return <>{children}</>; | ||
} | ||
|
||
return ( | ||
<section className={styles.section}> | ||
<h2 className={styles.heading}>Brak uprawnień</h2> | ||
<p className={styles.p}> | ||
Nie masz odpowiednich uprawnień, żeby korzystać z tej podstrony. W celu weryfikacji | ||
skontaktuj się z administracją serwisu. | ||
</p> | ||
</section> | ||
); | ||
}; |
File renamed without changes.
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,131 @@ | ||
import clsx from 'clsx'; | ||
import type { ChangeEventHandler, FormEvent } from 'react'; | ||
import { useRef, memo, useCallback, useEffect, useState } from 'react'; | ||
|
||
import { useMutation } from '../../hooks/useMutation'; | ||
import { useQuery } from '../../hooks/useQuery'; | ||
import type { BlogIdRequestBody } from '../../pages/api/blogs/[blogId]'; | ||
import { getBlog } from '../../utils/api/getBlog'; | ||
import { updateBlog } from '../../utils/api/updateBlog'; | ||
import { Button } from '../Button/Button'; | ||
|
||
import styles from './updateBlogForm.module.scss'; | ||
|
||
type Props = { | ||
readonly blogId: string; | ||
}; | ||
|
||
export const UpdateBlogForm = memo<Props>(({ blogId }) => { | ||
const { mutate, status } = useMutation((body: BlogIdRequestBody) => updateBlog(blogId, body)); | ||
const { value: blog, status: queryStatus } = useQuery( | ||
useCallback(() => getBlog(blogId), [blogId]), | ||
); | ||
const [fields, setFields] = useState<BlogIdRequestBody>({ | ||
href: '', | ||
creatorEmail: null, | ||
isPublic: false, | ||
}); | ||
const [isValid, setIsValid] = useState(false); | ||
const formRef = useRef<HTMLFormElement | null>(null); | ||
|
||
useEffect(() => { | ||
if (queryStatus === 'success' && blog) { | ||
setFields(blog); | ||
} | ||
}, [queryStatus, blog]); | ||
|
||
const handleChange: ChangeEventHandler<HTMLInputElement> = useCallback(({ currentTarget }) => { | ||
setFields((fields) => ({ ...fields, [currentTarget.name]: currentTarget.value })); | ||
setIsValid(formRef.current?.checkValidity() ?? false); | ||
}, []); | ||
|
||
const handleInputCheckboxChange: ChangeEventHandler<HTMLInputElement> = useCallback( | ||
({ currentTarget }) => { | ||
setFields((fields) => ({ ...fields, [currentTarget.name]: currentTarget.checked })); | ||
}, | ||
[], | ||
); | ||
|
||
const handleSubmit = useCallback( | ||
(e: FormEvent<HTMLFormElement>) => { | ||
e.preventDefault(); | ||
void mutate(fields); | ||
}, | ||
[mutate, fields], | ||
); | ||
|
||
if (!blog) { | ||
return null; | ||
} | ||
|
||
const isLoading = status === 'loading'; | ||
|
||
return ( | ||
<form | ||
onSubmit={handleSubmit} | ||
className={clsx(styles.form, isLoading && styles.formLoading)} | ||
ref={formRef} | ||
> | ||
<label className={styles.label}> | ||
Nazwa bloga | ||
<input className={styles.input} value={blog.name} type="text" name="name" readOnly /> | ||
</label> | ||
<label className={styles.label}> | ||
Href bloga | ||
<input | ||
className={styles.input} | ||
value={fields.href} | ||
name="href" | ||
onChange={handleChange} | ||
placeholder="Podaj href bloga" | ||
required | ||
type="url" | ||
/> | ||
</label> | ||
<label className={styles.label}> | ||
Rss bloga | ||
<input className={styles.input} value={blog.rss} name="rss" type="url" readOnly /> | ||
</label> | ||
<label className={styles.label}> | ||
Slug bloga | ||
<input className={styles.input} value={blog.slug ?? ''} readOnly name="slug" type="text" /> | ||
</label> | ||
<label className={styles.label}> | ||
Favicon bloga | ||
<input | ||
className={styles.input} | ||
value={blog.favicon ?? ''} | ||
name="favicon" | ||
type="url" | ||
readOnly | ||
/> | ||
</label> | ||
<label className={styles.label}> | ||
Email twórcy | ||
<input | ||
className={styles.input} | ||
value={fields.creatorEmail ?? ''} | ||
name="creatorEmail" | ||
onChange={handleChange} | ||
placeholder="Podaj email twórcy" | ||
type="email" | ||
/> | ||
</label> | ||
<label className={styles.labelCheckbox}> | ||
<input | ||
className={styles.inputCheckbox} | ||
checked={fields.isPublic} | ||
name="isPublic" | ||
onChange={handleInputCheckboxChange} | ||
type="checkbox" | ||
required | ||
/> | ||
Czy blog ma być pokazany na stronie? | ||
</label> | ||
<Button type="submit" disabled={!isValid} className={styles.submitButton}> | ||
Zapisz | ||
</Button> | ||
</form> | ||
); | ||
}); | ||
UpdateBlogForm.displayName = 'UpdateBlogForm'; |
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,18 @@ | ||
import { memo } from 'react'; | ||
|
||
import { UpdateBlogForm } from './UpdateBlogForm'; | ||
import styles from './updateBlogSection.module.css'; | ||
|
||
type Props = { | ||
readonly blogId: string; | ||
}; | ||
|
||
export const UpdateBlogSection = memo<Props>(({ blogId }) => { | ||
return ( | ||
<section className={styles.section}> | ||
<h2 className={styles.heading}>Formularz aktualizacji danych bloga</h2> | ||
<UpdateBlogForm blogId={blogId} /> | ||
</section> | ||
); | ||
}); | ||
UpdateBlogSection.displayName = 'UpdateBlogSection'; |
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,48 @@ | ||
.form { | ||
border-left: 3px solid var(--gray-dark-border); | ||
margin-top: 1rem; | ||
padding: 1.5rem; | ||
text-align: left; | ||
background: var(--white); | ||
box-shadow: 1px 1px 6px 1px var(--gray-shadow); | ||
border-radius: 3px; | ||
|
||
@media screen and (min-width: 45em) { | ||
padding: 2rem; | ||
margin-top: 2rem; | ||
} | ||
} | ||
|
||
.formLoading { | ||
cursor: wait; | ||
} | ||
|
||
.label { | ||
display: block; | ||
margin: 1rem 0; | ||
color: var(--black-lighter); | ||
|
||
&Checkbox { | ||
display: flex; | ||
align-items: center; | ||
} | ||
} | ||
|
||
.input { | ||
width: 100%; | ||
padding: 0.75rem 1rem; | ||
margin: 0.25rem 0; | ||
border-radius: 5px; | ||
border: 1px solid var(--gray-dark-border); | ||
|
||
&Checkbox { | ||
width: 1.25rem; | ||
height: 1.25rem; | ||
display: flex; | ||
margin-right: 0.75rem; | ||
} | ||
} | ||
|
||
.submitButton { | ||
margin-top: 1.75rem; | ||
} |
Oops, something went wrong.