mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 13:23:05 +01:00
Create snippet form and functionality
This commit is contained in:
@@ -1,16 +1,20 @@
|
|||||||
import { BrowserRouter, Switch, Route } from 'react-router-dom';
|
import { BrowserRouter, Switch, Route } from 'react-router-dom';
|
||||||
import { Navbar } from './components/Navigation/Navbar';
|
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 = () => {
|
export const App = () => {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Navbar />
|
<SnippetsContextProvider>
|
||||||
<Switch>
|
<Navbar />
|
||||||
<Route exact path='/' component={Home} />
|
<Switch>
|
||||||
<Route path='/snippets' component={Snippets} />
|
<Route exact path='/' component={Home} />
|
||||||
<Route path='/snippet/:id' component={Snippet} />
|
<Route path='/snippets' component={Snippets} />
|
||||||
</Switch>
|
<Route path='/snippet/:id' component={Snippet} />
|
||||||
|
<Route path='/editor' component={Editor} />
|
||||||
|
</Switch>
|
||||||
|
</SnippetsContextProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
{
|
{
|
||||||
"name": "Snippets",
|
"name": "Snippets",
|
||||||
"dest": "/snippets"
|
"dest": "/snippets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Editor",
|
||||||
|
"dest": "/editor"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
129
client/src/components/Snippets/SnippetForm.tsx
Normal file
129
client/src/components/Snippets/SnippetForm.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -7,6 +7,7 @@ interface Props {
|
|||||||
small?: boolean;
|
small?: boolean;
|
||||||
handler?: () => void;
|
handler?: () => void;
|
||||||
classes?: string;
|
classes?: string;
|
||||||
|
type?: 'button' | 'submit' | 'reset';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Button = (props: Props): JSX.Element => {
|
export const Button = (props: Props): JSX.Element => {
|
||||||
@@ -16,7 +17,8 @@ export const Button = (props: Props): JSX.Element => {
|
|||||||
outline = false,
|
outline = false,
|
||||||
small = false,
|
small = false,
|
||||||
handler,
|
handler,
|
||||||
classes = ''
|
classes = '',
|
||||||
|
type = 'button'
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const elClasses = [
|
const elClasses = [
|
||||||
@@ -27,7 +29,7 @@ export const Button = (props: Props): JSX.Element => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type='button' className={elClasses.join(' ')} onClick={handler}>
|
<button type={type} className={elClasses.join(' ')} onClick={handler}>
|
||||||
{text}
|
{text}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ interface Props {
|
|||||||
|
|
||||||
export const Layout = (props: Props): JSX.Element => {
|
export const Layout = (props: Props): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div className='container-fluid px-5'>
|
<div className='container-lg px-5'>
|
||||||
<div className='row pt-4'>{props.children}</div>
|
<div className='row pt-4'>{props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,4 +4,3 @@ export * from './Card';
|
|||||||
export * from './PageHeader';
|
export * from './PageHeader';
|
||||||
export * from './Spinner';
|
export * from './Spinner';
|
||||||
export * from './Button';
|
export * from './Button';
|
||||||
export * from './List';
|
|
||||||
|
|||||||
32
client/src/containers/Editor.tsx
Normal file
32
client/src/containers/Editor.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
4
client/src/containers/index.ts
Normal file
4
client/src/containers/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './Home';
|
||||||
|
export * from './Snippet';
|
||||||
|
export * from './Snippets';
|
||||||
|
export * from './Editor';
|
||||||
@@ -2,13 +2,10 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import './bootstrap.min.css';
|
import './bootstrap.min.css';
|
||||||
import { App } from './App';
|
import { App } from './App';
|
||||||
import { SnippetsContextProvider } from './store';
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<SnippetsContextProvider>
|
<App />
|
||||||
<App />
|
|
||||||
</SnippetsContextProvider>
|
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import { useState, createContext } from 'react';
|
import { useState, createContext } from 'react';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {
|
import {
|
||||||
Context,
|
Context,
|
||||||
Snippet,
|
Snippet,
|
||||||
Response,
|
Response,
|
||||||
LanguageCount
|
LanguageCount,
|
||||||
|
NewSnippet
|
||||||
} from '../typescript/interfaces';
|
} from '../typescript/interfaces';
|
||||||
|
|
||||||
export const SnippetsContext = createContext<Context>({
|
export const SnippetsContext = createContext<Context>({
|
||||||
snippets: [],
|
snippets: [],
|
||||||
|
currentSnippet: null,
|
||||||
languageCount: [],
|
languageCount: [],
|
||||||
getSnippets: () => {},
|
getSnippets: () => {},
|
||||||
|
getSnippetById: (id: number) => {},
|
||||||
|
setSnippet: (id: number) => {},
|
||||||
|
createSnippet: (snippet: NewSnippet) => {},
|
||||||
countSnippets: () => {}
|
countSnippets: () => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -20,8 +26,11 @@ interface Props {
|
|||||||
|
|
||||||
export const SnippetsContextProvider = (props: Props): JSX.Element => {
|
export const SnippetsContextProvider = (props: Props): JSX.Element => {
|
||||||
const [snippets, setSnippets] = useState<Snippet[]>([]);
|
const [snippets, setSnippets] = useState<Snippet[]>([]);
|
||||||
|
const [currentSnippet, setCurrentSnippet] = useState<Snippet | null>(null);
|
||||||
const [languageCount, setLanguageCount] = useState<LanguageCount[]>([]);
|
const [languageCount, setLanguageCount] = useState<LanguageCount[]>([]);
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
const getSnippets = (): void => {
|
const getSnippets = (): void => {
|
||||||
axios
|
axios
|
||||||
.get<Response<Snippet[]>>('/api/snippets')
|
.get<Response<Snippet[]>>('/api/snippets')
|
||||||
@@ -29,6 +38,37 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
|
|||||||
.catch(err => console.log(err));
|
.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 => {
|
const countSnippets = (): void => {
|
||||||
axios
|
axios
|
||||||
.get<Response<LanguageCount[]>>('/api/snippets/statistics/count')
|
.get<Response<LanguageCount[]>>('/api/snippets/statistics/count')
|
||||||
@@ -38,8 +78,12 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
|
|||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
snippets,
|
snippets,
|
||||||
|
currentSnippet,
|
||||||
languageCount,
|
languageCount,
|
||||||
getSnippets,
|
getSnippets,
|
||||||
|
getSnippetById,
|
||||||
|
setSnippet,
|
||||||
|
createSnippet,
|
||||||
countSnippets
|
countSnippets
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { LanguageCount, Snippet } from '.';
|
import { LanguageCount, NewSnippet, Snippet } from '.';
|
||||||
|
|
||||||
export interface Context {
|
export interface Context {
|
||||||
snippets: Snippet[];
|
snippets: Snippet[];
|
||||||
|
currentSnippet: Snippet | null;
|
||||||
languageCount: LanguageCount[];
|
languageCount: LanguageCount[];
|
||||||
getSnippets: () => void;
|
getSnippets: () => void;
|
||||||
|
getSnippetById: (id: number) => void;
|
||||||
|
setSnippet: (id: number) => void;
|
||||||
|
createSnippet: (snippet: NewSnippet) => void;
|
||||||
countSnippets: () => void;
|
countSnippets: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user