mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 13:23:05 +01:00
React client with context for state management
This commit is contained in:
13
client/package-lock.json
generated
13
client/package-lock.json
generated
@@ -3178,6 +3178,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.3.tgz",
|
||||||
"integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA=="
|
"integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA=="
|
||||||
},
|
},
|
||||||
|
"axios": {
|
||||||
|
"version": "0.21.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||||
|
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"axobject-query": {
|
"axobject-query": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||||
@@ -4907,6 +4915,11 @@
|
|||||||
"whatwg-url": "^8.0.0"
|
"whatwg-url": "^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"version": "1.10.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz",
|
||||||
|
"integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig=="
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.2",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
"@types/node": "^12.20.25",
|
"@types/node": "^12.20.25",
|
||||||
"@types/react": "^17.0.21",
|
"@types/react": "^17.0.21",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
|
"axios": "^0.21.4",
|
||||||
|
"dayjs": "^1.10.7",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router-dom": "^5.3.0",
|
"react-router-dom": "^5.3.0",
|
||||||
@@ -44,5 +46,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react-router-dom": "^5.1.9"
|
"@types/react-router-dom": "^5.1.9"
|
||||||
}
|
},
|
||||||
|
"proxy": "http://localhost:5000"
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
client/public/favicon.ico
Normal file
BIN
client/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
43
client/public/index.html
Normal file
43
client/public/index.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
client/public/logo192.png
Normal file
BIN
client/public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
client/public/logo512.png
Normal file
BIN
client/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
client/public/manifest.json
Normal file
25
client/public/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
3
client/public/robots.txt
Normal file
3
client/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
16
client/src/App.tsx
Normal file
16
client/src/App.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { BrowserRouter, Switch, Route } from 'react-router-dom';
|
||||||
|
import { Navbar } from './components/Navigation/Navbar';
|
||||||
|
import { Home, Snippet, Snippets } from './containers';
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Navbar />
|
||||||
|
<Switch>
|
||||||
|
<Route exact path='/' component={Home} />
|
||||||
|
<Route path='/snippets' component={Snippets} />
|
||||||
|
<Route path='/snippet/:id' component={Snippet} />
|
||||||
|
</Switch>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
};
|
||||||
12
client/src/bootstrap.min.css
vendored
Normal file
12
client/src/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
13
client/src/index.css
Normal file
13
client/src/index.css
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
14
client/src/index.tsx
Normal file
14
client/src/index.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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')
|
||||||
|
);
|
||||||
51
client/src/store/SnippetsContext.tsx
Normal file
51
client/src/store/SnippetsContext.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useState, createContext } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import {
|
||||||
|
Context,
|
||||||
|
Snippet,
|
||||||
|
Response,
|
||||||
|
LanguageCount
|
||||||
|
} from '../typescript/interfaces';
|
||||||
|
|
||||||
|
export const SnippetsContext = createContext<Context>({
|
||||||
|
snippets: [],
|
||||||
|
languageCount: [],
|
||||||
|
getSnippets: () => {},
|
||||||
|
countSnippets: () => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: JSX.Element | JSX.Element[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SnippetsContextProvider = (props: Props): JSX.Element => {
|
||||||
|
const [snippets, setSnippets] = useState<Snippet[]>([]);
|
||||||
|
const [languageCount, setLanguageCount] = useState<LanguageCount[]>([]);
|
||||||
|
|
||||||
|
const getSnippets = (): void => {
|
||||||
|
axios
|
||||||
|
.get<Response<Snippet[]>>('/api/snippets')
|
||||||
|
.then(res => setSnippets(res.data.data))
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const countSnippets = (): void => {
|
||||||
|
axios
|
||||||
|
.get<Response<LanguageCount[]>>('/api/snippets/statistics/count')
|
||||||
|
.then(res => setLanguageCount(res.data.data))
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
snippets,
|
||||||
|
languageCount,
|
||||||
|
getSnippets,
|
||||||
|
countSnippets
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SnippetsContext.Provider value={context}>
|
||||||
|
{props.children}
|
||||||
|
</SnippetsContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
client/src/store/index.ts
Normal file
1
client/src/store/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './SnippetsContext';
|
||||||
8
client/src/typescript/interfaces/Context.ts
Normal file
8
client/src/typescript/interfaces/Context.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { LanguageCount, Snippet } from '.';
|
||||||
|
|
||||||
|
export interface Context {
|
||||||
|
snippets: Snippet[];
|
||||||
|
languageCount: LanguageCount[];
|
||||||
|
getSnippets: () => void;
|
||||||
|
countSnippets: () => void;
|
||||||
|
}
|
||||||
5
client/src/typescript/interfaces/Model.ts
Normal file
5
client/src/typescript/interfaces/Model.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface Model {
|
||||||
|
id: number;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
3
client/src/typescript/interfaces/Response.ts
Normal file
3
client/src/typescript/interfaces/Response.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface Response<T> {
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
4
client/src/typescript/interfaces/Route.ts
Normal file
4
client/src/typescript/interfaces/Route.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface Route {
|
||||||
|
name: string;
|
||||||
|
dest: string;
|
||||||
|
}
|
||||||
11
client/src/typescript/interfaces/Snippet.ts
Normal file
11
client/src/typescript/interfaces/Snippet.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Model } from '.';
|
||||||
|
|
||||||
|
export interface NewSnippet {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
language: string;
|
||||||
|
code: string;
|
||||||
|
docs?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Snippet extends Model, NewSnippet {}
|
||||||
4
client/src/typescript/interfaces/Statistics.ts
Normal file
4
client/src/typescript/interfaces/Statistics.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface LanguageCount {
|
||||||
|
count: number;
|
||||||
|
language: string;
|
||||||
|
}
|
||||||
6
client/src/typescript/interfaces/index.ts
Normal file
6
client/src/typescript/interfaces/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export * from './Model';
|
||||||
|
export * from './Snippet';
|
||||||
|
export * from './Route';
|
||||||
|
export * from './Response';
|
||||||
|
export * from './Context';
|
||||||
|
export * from './Statistics';
|
||||||
17
client/src/utils/dateParser.ts
Normal file
17
client/src/utils/dateParser.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
|
||||||
|
interface Return {
|
||||||
|
formatted: string;
|
||||||
|
relative: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dateParser = (date: Date): Return => {
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
|
const parsedDate = dayjs(date);
|
||||||
|
const formatted = parsedDate.format('YYYY-MM-DD HH:mm');
|
||||||
|
const relative = parsedDate.fromNow();
|
||||||
|
|
||||||
|
return { formatted, relative };
|
||||||
|
};
|
||||||
1
client/src/utils/index.ts
Normal file
1
client/src/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dateParser';
|
||||||
6
nodemon.json
Normal file
6
nodemon.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"watch": ["src"],
|
||||||
|
"ext": "ts,json",
|
||||||
|
"ignore": ["src/**/*.spec.ts"],
|
||||||
|
"exec": "ts-node ./src/server.ts"
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { QueryTypes } from 'sequelize';
|
||||||
|
import { sequelize } from '../db';
|
||||||
import { asyncWrapper } from '../middleware';
|
import { asyncWrapper } from '../middleware';
|
||||||
import { SnippetModel } from '../models';
|
import { SnippetModel } from '../models';
|
||||||
import { ErrorResponse } from '../utils';
|
import { ErrorResponse } from '../utils';
|
||||||
@@ -114,3 +116,30 @@ export const deleteSnippet = asyncWrapper(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Count snippets by language
|
||||||
|
* @route /api/snippets/statistics/count
|
||||||
|
* @request GET
|
||||||
|
*/
|
||||||
|
export const countSnippets = asyncWrapper(
|
||||||
|
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
||||||
|
const result = await sequelize.query(
|
||||||
|
`SELECT
|
||||||
|
COUNT(language) AS count,
|
||||||
|
language
|
||||||
|
FROM snippets
|
||||||
|
GROUP BY language
|
||||||
|
ORDER BY
|
||||||
|
count DESC,
|
||||||
|
language ASC`,
|
||||||
|
{
|
||||||
|
type: QueryTypes.SELECT
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
data: result
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
SnippetCreationAttributes
|
SnippetCreationAttributes
|
||||||
} from '../../typescript/interfaces';
|
} from '../../typescript/interfaces';
|
||||||
|
|
||||||
const { INTEGER, STRING, DATE } = DataTypes;
|
const { INTEGER, STRING, DATE, TEXT } = DataTypes;
|
||||||
|
|
||||||
export const up = async (queryInterface: QueryInterface): Promise<void> => {
|
export const up = async (queryInterface: QueryInterface): Promise<void> => {
|
||||||
await queryInterface.createTable<Model<Snippet, SnippetCreationAttributes>>(
|
await queryInterface.createTable<Model<Snippet, SnippetCreationAttributes>>(
|
||||||
@@ -20,10 +20,24 @@ export const up = async (queryInterface: QueryInterface): Promise<void> => {
|
|||||||
type: STRING,
|
type: STRING,
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
|
description: {
|
||||||
|
type: TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
language: {
|
language: {
|
||||||
type: STRING,
|
type: STRING,
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
|
code: {
|
||||||
|
type: TEXT,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
type: TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
type: DATE,
|
type: DATE,
|
||||||
allowNull: false
|
allowNull: false
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Model, DataTypes } from 'sequelize';
|
|||||||
import { sequelize } from '../db';
|
import { sequelize } from '../db';
|
||||||
import { Snippet, SnippetCreationAttributes } from '../typescript/interfaces';
|
import { Snippet, SnippetCreationAttributes } from '../typescript/interfaces';
|
||||||
|
|
||||||
const { INTEGER, STRING, DATE } = DataTypes;
|
const { INTEGER, STRING, DATE, TEXT } = DataTypes;
|
||||||
|
|
||||||
interface SnippetInstance
|
interface SnippetInstance
|
||||||
extends Model<Snippet, SnippetCreationAttributes>,
|
extends Model<Snippet, SnippetCreationAttributes>,
|
||||||
@@ -18,10 +18,24 @@ export const SnippetModel = sequelize.define<SnippetInstance>('Snippet', {
|
|||||||
type: STRING,
|
type: STRING,
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
|
description: {
|
||||||
|
type: TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
language: {
|
language: {
|
||||||
type: STRING,
|
type: STRING,
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
|
code: {
|
||||||
|
type: TEXT,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
type: TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
type: DATE
|
type: DATE
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import {
|
import {
|
||||||
|
countSnippets,
|
||||||
createSnippet,
|
createSnippet,
|
||||||
deleteSnippet,
|
deleteSnippet,
|
||||||
getAllSnippets,
|
getAllSnippets,
|
||||||
@@ -12,7 +13,7 @@ export const snippetRouter = Router();
|
|||||||
|
|
||||||
snippetRouter
|
snippetRouter
|
||||||
.route('/')
|
.route('/')
|
||||||
.post(requireBody('title', 'language'), createSnippet)
|
.post(requireBody('title', 'language', 'code'), createSnippet)
|
||||||
.get(getAllSnippets);
|
.get(getAllSnippets);
|
||||||
|
|
||||||
snippetRouter
|
snippetRouter
|
||||||
@@ -20,3 +21,5 @@ snippetRouter
|
|||||||
.get(getSnippet)
|
.get(getSnippet)
|
||||||
.put(updateSnippet)
|
.put(updateSnippet)
|
||||||
.delete(deleteSnippet);
|
.delete(deleteSnippet);
|
||||||
|
|
||||||
|
snippetRouter.route('/statistics/count').get(countSnippets);
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import { Optional } from 'sequelize';
|
|||||||
|
|
||||||
export interface Snippet extends Model {
|
export interface Snippet extends Model {
|
||||||
title: string;
|
title: string;
|
||||||
|
description: string;
|
||||||
language: string;
|
language: string;
|
||||||
|
code: string;
|
||||||
|
docs: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnippetCreationAttributes
|
export interface SnippetCreationAttributes
|
||||||
|
|||||||
Reference in New Issue
Block a user