Create snippet form and functionality

This commit is contained in:
unknown
2021-09-22 12:24:11 +02:00
parent 952ebb5d3a
commit f7614302a3
11 changed files with 236 additions and 17 deletions

View File

@@ -1,16 +1,20 @@
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { Navbar } from './components/Navigation/Navbar';
import { Home, Snippet, Snippets } from './containers';
import { Editor, Home, Snippet, Snippets } from './containers';
import { SnippetsContextProvider } from './store';
export const App = () => {
return (
<BrowserRouter>
<SnippetsContextProvider>
<Navbar />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/snippets' component={Snippets} />
<Route path='/snippet/:id' component={Snippet} />
<Route path='/editor' component={Editor} />
</Switch>
</SnippetsContextProvider>
</BrowserRouter>
);
};

View File

@@ -7,6 +7,10 @@
{
"name": "Snippets",
"dest": "/snippets"
},
{
"name": "Editor",
"dest": "/editor"
}
]
}

View File

@@ -0,0 +1,129 @@
import { ChangeEvent, FormEvent, Fragment, useState, useContext } from 'react';
import { SnippetsContext } from '../../store';
import { NewSnippet } from '../../typescript/interfaces';
import { Button, Card } from '../UI';
export const SnippetForm = (): JSX.Element => {
const { createSnippet } = useContext(SnippetsContext);
const [formData, setFormData] = useState<NewSnippet>({
title: '',
description: '',
language: '',
code: '',
docs: ''
});
const inputHandler = (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
const formHandler = (e: FormEvent) => {
e.preventDefault();
createSnippet(formData);
};
return (
<Fragment>
<div className='col-12 mt-3'>
<Card>
<form onSubmit={e => formHandler(e)}>
{/* DETAILS SECTION */}
<h5 className='card-title mb-3'>Snippet details</h5>
{/* TITLE */}
<div className='mb-3'>
<label htmlFor='title' className='form-label'>
Title
</label>
<input
type='text'
className='form-control'
id='title'
name='title'
value={formData.title}
placeholder='Recursively copy all files'
required
onChange={e => inputHandler(e)}
/>
</div>
{/* DESCRIPTION */}
<div className='mb-3'>
<label htmlFor='description' className='form-label'>
Short description
</label>
<input
type='text'
className='form-control'
id='description'
name='description'
value={formData.description}
placeholder='Bash script to copy all files from src to dest'
onChange={e => inputHandler(e)}
/>
</div>
{/* LANGUAGE */}
<div className='mb-3'>
<label htmlFor='language' className='form-label'>
Language
</label>
<input
type='text'
className='form-control'
id='language'
name='language'
value={formData.language}
placeholder='bash'
required
onChange={e => inputHandler(e)}
/>
</div>
<hr />
{/* CODE SECTION */}
<h5 className='card-title mb-3'>Snippet code</h5>
<div className='mb-3'>
<textarea
className='form-control'
id='code'
name='code'
rows={10}
value={formData.code}
placeholder='cp -r ./src ./dest'
required
onChange={e => inputHandler(e)}
></textarea>
</div>
<hr />
{/* DOCS SECTION */}
<h5 className='card-title mb-3'>Snippet documentation</h5>
<div className='mb-3'>
<textarea
className='form-control'
id='docs'
name='docs'
rows={10}
value={formData.docs}
placeholder='`-r` flag stands for `--recursive`'
onChange={e => inputHandler(e)}
></textarea>
</div>
{/* SUBMIT SECTION */}
<div className='d-grid'>
<Button text='Create snippet' color='dark' type='submit' />
</div>
</form>
</Card>
</div>
</Fragment>
);
};

View File

@@ -7,6 +7,7 @@ interface Props {
small?: boolean;
handler?: () => void;
classes?: string;
type?: 'button' | 'submit' | 'reset';
}
export const Button = (props: Props): JSX.Element => {
@@ -16,7 +17,8 @@ export const Button = (props: Props): JSX.Element => {
outline = false,
small = false,
handler,
classes = ''
classes = '',
type = 'button'
} = props;
const elClasses = [
@@ -27,7 +29,7 @@ export const Button = (props: Props): JSX.Element => {
];
return (
<button type='button' className={elClasses.join(' ')} onClick={handler}>
<button type={type} className={elClasses.join(' ')} onClick={handler}>
{text}
</button>
);

View File

@@ -4,7 +4,7 @@ interface Props {
export const Layout = (props: Props): JSX.Element => {
return (
<div className='container-fluid px-5'>
<div className='container-lg px-5'>
<div className='row pt-4'>{props.children}</div>
</div>
);

View File

@@ -4,4 +4,3 @@ export * from './Card';
export * from './PageHeader';
export * from './Spinner';
export * from './Button';
export * from './List';

View File

@@ -0,0 +1,32 @@
import { Fragment } from 'react';
import { useLocation } from 'react-router-dom';
import { SnippetForm } from '../components/Snippets/SnippetForm';
import { Layout, PageHeader } from '../components/UI';
import { Snippet } from '../typescript/interfaces';
interface Props {
snippet?: Snippet;
}
export const Editor = (props: Props): JSX.Element => {
const { snippet } = props;
// Get previous location
const location = useLocation<{ from: string }>();
const { from } = location.state || '/snippets';
return (
<Layout>
{snippet ? (
<Fragment>
<PageHeader title='edit snippet' prevDest={from} />
</Fragment>
) : (
<Fragment>
<PageHeader title='Add new snippet' />
<SnippetForm />
</Fragment>
)}
</Layout>
);
};

View File

@@ -0,0 +1,4 @@
export * from './Home';
export * from './Snippet';
export * from './Snippets';
export * from './Editor';

View File

@@ -2,13 +2,10 @@ import React from 'react';
import ReactDOM from 'react-dom';
import './bootstrap.min.css';
import { App } from './App';
import { SnippetsContextProvider } from './store';
ReactDOM.render(
<React.StrictMode>
<SnippetsContextProvider>
<App />
</SnippetsContextProvider>
</React.StrictMode>,
document.getElementById('root')
);

View File

@@ -1,16 +1,22 @@
import { useState, createContext } from 'react';
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import {
Context,
Snippet,
Response,
LanguageCount
LanguageCount,
NewSnippet
} from '../typescript/interfaces';
export const SnippetsContext = createContext<Context>({
snippets: [],
currentSnippet: null,
languageCount: [],
getSnippets: () => {},
getSnippetById: (id: number) => {},
setSnippet: (id: number) => {},
createSnippet: (snippet: NewSnippet) => {},
countSnippets: () => {}
});
@@ -20,8 +26,11 @@ interface Props {
export const SnippetsContextProvider = (props: Props): JSX.Element => {
const [snippets, setSnippets] = useState<Snippet[]>([]);
const [currentSnippet, setCurrentSnippet] = useState<Snippet | null>(null);
const [languageCount, setLanguageCount] = useState<LanguageCount[]>([]);
const history = useHistory();
const getSnippets = (): void => {
axios
.get<Response<Snippet[]>>('/api/snippets')
@@ -29,6 +38,37 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
.catch(err => console.log(err));
};
const getSnippetById = (id: number): void => {
axios
.get<Response<Snippet>>(`/api/snippets/${id}`)
.then(res => setCurrentSnippet(res.data.data))
.catch(err => console.log(err));
};
const setSnippet = (id: number): void => {
if (id < 0) {
setCurrentSnippet(null);
return;
}
const snippet = snippets.find(s => s.id === id);
if (snippet) {
setCurrentSnippet(snippet);
}
};
const createSnippet = (snippet: NewSnippet): void => {
axios
.post<Response<Snippet>>('/api/snippets', snippet)
.then(res => {
setSnippets([...snippets, res.data.data]);
setCurrentSnippet(res.data.data);
history.push(`/snippet/${res.data.data.id}`);
})
.catch(err => console.log(err));
};
const countSnippets = (): void => {
axios
.get<Response<LanguageCount[]>>('/api/snippets/statistics/count')
@@ -38,8 +78,12 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
const context = {
snippets,
currentSnippet,
languageCount,
getSnippets,
getSnippetById,
setSnippet,
createSnippet,
countSnippets
};

View File

@@ -1,8 +1,12 @@
import { LanguageCount, Snippet } from '.';
import { LanguageCount, NewSnippet, Snippet } from '.';
export interface Context {
snippets: Snippet[];
currentSnippet: Snippet | null;
languageCount: LanguageCount[];
getSnippets: () => void;
getSnippetById: (id: number) => void;
setSnippet: (id: number) => void;
createSnippet: (snippet: NewSnippet) => void;
countSnippets: () => void;
}