mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 21:33:10 +01:00
Edit and delete snippet functionality
This commit is contained in:
@@ -12,7 +12,7 @@ export const App = () => {
|
|||||||
<Route exact path='/' component={Home} />
|
<Route exact path='/' component={Home} />
|
||||||
<Route path='/snippets' component={Snippets} />
|
<Route path='/snippets' component={Snippets} />
|
||||||
<Route path='/snippet/:id' component={Snippet} />
|
<Route path='/snippet/:id' component={Snippet} />
|
||||||
<Route path='/editor' component={Editor} />
|
<Route path='/editor/:id?' component={Editor} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</SnippetsContextProvider>
|
</SnippetsContextProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|||||||
84
client/src/components/Snippets/SnippetDetails.tsx
Normal file
84
client/src/components/Snippets/SnippetDetails.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { SnippetsContext } from '../../store';
|
||||||
|
import { Snippet } from '../../typescript/interfaces';
|
||||||
|
import { dateParser } from '../../utils';
|
||||||
|
import { Button, Card } from '../UI';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
snippet: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SnippetDetails = (props: Props): JSX.Element => {
|
||||||
|
const { title, language, createdAt, updatedAt, description, code, id } =
|
||||||
|
props.snippet;
|
||||||
|
|
||||||
|
const { deleteSnippet } = useContext(SnippetsContext);
|
||||||
|
|
||||||
|
const creationDate = dateParser(createdAt);
|
||||||
|
const updateDate = dateParser(updatedAt);
|
||||||
|
|
||||||
|
const copyHandler = () => {
|
||||||
|
navigator.clipboard.writeText(code);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title={title}>
|
||||||
|
<p>{description}</p>
|
||||||
|
|
||||||
|
{/* LANGUAGE */}
|
||||||
|
<div className={`d-flex justify-content-between`}>
|
||||||
|
<span>Language</span>
|
||||||
|
<span className='fw-bold'>{language}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CREATED AT */}
|
||||||
|
<div className={`d-flex justify-content-between`}>
|
||||||
|
<span>Created</span>
|
||||||
|
<span>{creationDate.relative}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* UPDATED AT */}
|
||||||
|
<div className={`d-flex justify-content-between`}>
|
||||||
|
<span>Last updated</span>
|
||||||
|
<span>{updateDate.relative}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
{/* ACTIONS */}
|
||||||
|
<div className='d-flex justify-content-between'>
|
||||||
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: `/editor/${id}`,
|
||||||
|
state: { from: window.location.pathname }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button text='Edit' color='dark' small outline classes='me-3' />
|
||||||
|
</Link>
|
||||||
|
<Button
|
||||||
|
text='Pin to homescreen'
|
||||||
|
color='dark'
|
||||||
|
small
|
||||||
|
outline
|
||||||
|
handler={copyHandler}
|
||||||
|
classes='me-3'
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
text='Delete'
|
||||||
|
color='danger'
|
||||||
|
small
|
||||||
|
outline
|
||||||
|
handler={() => deleteSnippet(id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
{/* COPY */}
|
||||||
|
<div className='d-grid'>
|
||||||
|
<Button text='Copy code' color='dark' small handler={copyHandler} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,10 +1,23 @@
|
|||||||
import { ChangeEvent, FormEvent, Fragment, useState, useContext } from 'react';
|
import {
|
||||||
|
ChangeEvent,
|
||||||
|
FormEvent,
|
||||||
|
Fragment,
|
||||||
|
useState,
|
||||||
|
useContext,
|
||||||
|
useEffect
|
||||||
|
} from 'react';
|
||||||
import { SnippetsContext } from '../../store';
|
import { SnippetsContext } from '../../store';
|
||||||
import { NewSnippet } from '../../typescript/interfaces';
|
import { NewSnippet } from '../../typescript/interfaces';
|
||||||
import { Button, Card } from '../UI';
|
import { Button, Card } from '../UI';
|
||||||
|
|
||||||
export const SnippetForm = (): JSX.Element => {
|
interface Props {
|
||||||
const { createSnippet } = useContext(SnippetsContext);
|
inEdit?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SnippetForm = (props: Props): JSX.Element => {
|
||||||
|
const { inEdit = false } = props;
|
||||||
|
const { createSnippet, currentSnippet, updateSnippet } =
|
||||||
|
useContext(SnippetsContext);
|
||||||
|
|
||||||
const [formData, setFormData] = useState<NewSnippet>({
|
const [formData, setFormData] = useState<NewSnippet>({
|
||||||
title: '',
|
title: '',
|
||||||
@@ -14,6 +27,14 @@ export const SnippetForm = (): JSX.Element => {
|
|||||||
docs: ''
|
docs: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inEdit) {
|
||||||
|
if (currentSnippet) {
|
||||||
|
setFormData({ ...currentSnippet });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [currentSnippet]);
|
||||||
|
|
||||||
const inputHandler = (
|
const inputHandler = (
|
||||||
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||||
) => {
|
) => {
|
||||||
@@ -25,7 +46,14 @@ export const SnippetForm = (): JSX.Element => {
|
|||||||
|
|
||||||
const formHandler = (e: FormEvent) => {
|
const formHandler = (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
createSnippet(formData);
|
|
||||||
|
if (inEdit) {
|
||||||
|
if (currentSnippet) {
|
||||||
|
updateSnippet(formData, currentSnippet.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
createSnippet(formData);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -119,7 +147,11 @@ export const SnippetForm = (): JSX.Element => {
|
|||||||
|
|
||||||
{/* SUBMIT SECTION */}
|
{/* SUBMIT SECTION */}
|
||||||
<div className='d-grid'>
|
<div className='d-grid'>
|
||||||
<Button text='Create snippet' color='dark' type='submit' />
|
<Button
|
||||||
|
text={`${inEdit ? 'Update snippet' : 'Create snippet'}`}
|
||||||
|
color='dark'
|
||||||
|
type='submit'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,25 +1,41 @@
|
|||||||
import { Fragment } from 'react';
|
import { Fragment, useEffect, useContext, useState } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { SnippetForm } from '../components/Snippets/SnippetForm';
|
import { SnippetForm } from '../components/Snippets/SnippetForm';
|
||||||
import { Layout, PageHeader } from '../components/UI';
|
import { Layout, PageHeader } from '../components/UI';
|
||||||
|
import { SnippetsContext } from '../store';
|
||||||
import { Snippet } from '../typescript/interfaces';
|
import { Snippet } from '../typescript/interfaces';
|
||||||
|
|
||||||
interface Props {
|
interface Params {
|
||||||
snippet?: Snippet;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Editor = (props: Props): JSX.Element => {
|
export const Editor = (): JSX.Element => {
|
||||||
const { snippet } = props;
|
const { setSnippet: setCurrentSnippet } = useContext(SnippetsContext);
|
||||||
|
const [inEdit, setInEdit] = useState(false);
|
||||||
|
|
||||||
// Get previous location
|
// Get previous location
|
||||||
const location = useLocation<{ from: string }>();
|
const location = useLocation<{ from: string }>();
|
||||||
const { from } = location.state || '/snippets';
|
const { from } = location.state || '/snippets';
|
||||||
|
|
||||||
|
// Get id
|
||||||
|
const { id } = useParams<Params>();
|
||||||
|
|
||||||
|
// Set snippet
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentSnippet(-1);
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
setCurrentSnippet(+id);
|
||||||
|
setInEdit(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
{snippet ? (
|
{inEdit ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<PageHeader title='edit snippet' prevDest={from} />
|
<PageHeader title='Edit snippet' prevDest={from} />
|
||||||
|
<SnippetForm inEdit />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : (
|
) : (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export const SnippetsContext = createContext<Context>({
|
|||||||
getSnippetById: (id: number) => {},
|
getSnippetById: (id: number) => {},
|
||||||
setSnippet: (id: number) => {},
|
setSnippet: (id: number) => {},
|
||||||
createSnippet: (snippet: NewSnippet) => {},
|
createSnippet: (snippet: NewSnippet) => {},
|
||||||
|
updateSnippet: (snippet: NewSnippet, id: number) => {},
|
||||||
|
deleteSnippet: (id: number) => {},
|
||||||
countSnippets: () => {}
|
countSnippets: () => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -69,6 +71,39 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
|
|||||||
.catch(err => console.log(err));
|
.catch(err => console.log(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateSnippet = (snippet: NewSnippet, id: number): void => {
|
||||||
|
axios
|
||||||
|
.put<Response<Snippet>>(`/api/snippets/${id}`, snippet)
|
||||||
|
.then(res => {
|
||||||
|
const oldSnippetIdx = snippets.findIndex(s => s.id === id);
|
||||||
|
setSnippets([
|
||||||
|
...snippets.slice(0, oldSnippetIdx),
|
||||||
|
res.data.data,
|
||||||
|
...snippets.slice(oldSnippetIdx + 1)
|
||||||
|
]);
|
||||||
|
setCurrentSnippet(res.data.data);
|
||||||
|
history.push(`/snippet/${res.data.data.id}`, { from: '/snippets' });
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSnippet = (id: number): void => {
|
||||||
|
if (window.confirm('Are you sure you want to delete this snippet?')) {
|
||||||
|
axios
|
||||||
|
.delete<Response<{}>>(`/api/snippets/${id}`)
|
||||||
|
.then(res => {
|
||||||
|
const deletedSnippetIdx = snippets.findIndex(s => s.id === id);
|
||||||
|
setSnippets([
|
||||||
|
...snippets.slice(0, deletedSnippetIdx),
|
||||||
|
...snippets.slice(deletedSnippetIdx + 1)
|
||||||
|
]);
|
||||||
|
setSnippet(-1);
|
||||||
|
history.push('/snippets');
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const countSnippets = (): void => {
|
const countSnippets = (): void => {
|
||||||
axios
|
axios
|
||||||
.get<Response<LanguageCount[]>>('/api/snippets/statistics/count')
|
.get<Response<LanguageCount[]>>('/api/snippets/statistics/count')
|
||||||
@@ -84,6 +119,8 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
|
|||||||
getSnippetById,
|
getSnippetById,
|
||||||
setSnippet,
|
setSnippet,
|
||||||
createSnippet,
|
createSnippet,
|
||||||
|
updateSnippet,
|
||||||
|
deleteSnippet,
|
||||||
countSnippets
|
countSnippets
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,5 +8,7 @@ export interface Context {
|
|||||||
getSnippetById: (id: number) => void;
|
getSnippetById: (id: number) => void;
|
||||||
setSnippet: (id: number) => void;
|
setSnippet: (id: number) => void;
|
||||||
createSnippet: (snippet: NewSnippet) => void;
|
createSnippet: (snippet: NewSnippet) => void;
|
||||||
|
updateSnippet: (snippet: NewSnippet, id: number) => void;
|
||||||
|
deleteSnippet: (id: number) => void;
|
||||||
countSnippets: () => void;
|
countSnippets: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user