commit
8af5253a00
@ -0,0 +1,26 @@ |
|||||||
|
# RoboTop сайт робототехнического фестиваля |
||||||
|
|
||||||
|
Сайт для мероприятия с возможностью регистрировать пользователей в безе данный MySql. Построен на: |
||||||
|
|
||||||
|
- Сайт написан на [Typescript](https://www.typescriptlang.org/) |
||||||
|
- Написание постов [MDX](https://mdxjs.com/) |
||||||
|
- Стили и дизайн [Tailwind CSS](https://tailwindcss.com/) |
||||||
|
- Статически анализ кода [ESLint](https://eslint.org/) |
||||||
|
- Linting, проверка типов и форматирование включены по умолчанию. [`husky`](https://github.com/typicode/husky) |
||||||
|
- Тестирование с [Jest](https://jestjs.io/) и [`react-testing-library`](https://testing-library.com/docs/react-testing-library/intro) |
||||||
|
- [Redux Toolkit](https://redux-toolkit.js.org/) |
||||||
|
- [Mysql2](https://www.npmjs.com/package/mysql2) |
||||||
|
|
||||||
|
## Для запуска сайта: |
||||||
|
|
||||||
|
```bash |
||||||
|
git clone http://62.113.100.171:3000/Lab/robotop.krasnikov.pro.git |
||||||
|
cd robotop.krasnikov.pro |
||||||
|
|
||||||
|
npm install |
||||||
|
|
||||||
|
npm run dev |
||||||
|
|
||||||
|
Ваш новый сайт будет доступен в http://localhost:3000/ |
||||||
|
|
||||||
|
Для настройки подключения к базе данных отредактируйте файл env.local и переименуйте его в .env.local |
@ -0,0 +1,45 @@ |
|||||||
|
import NextHead from 'next/head'; |
||||||
|
import { useRouter } from 'next/router'; |
||||||
|
import React from 'react'; |
||||||
|
import { MetaProps } from '../types/layout'; |
||||||
|
|
||||||
|
export type WithYandexMetrikaProps = { |
||||||
|
children: React.ReactNode; |
||||||
|
} |
||||||
|
|
||||||
|
export const WEBSITE_HOST_URL = 'https://robotop.krasnikov.pro'; |
||||||
|
|
||||||
|
const Head = ({ customMeta }: { customMeta?: MetaProps }): JSX.Element => { |
||||||
|
const router = useRouter(); |
||||||
|
const meta: MetaProps = { |
||||||
|
title: 'РоботТоп - робототехнический фестиваль', |
||||||
|
description: |
||||||
|
'РоботТОП – это робототехнические соревнования, в которых могут принять участие молодые любители робототехники, объединившись в команды.', |
||||||
|
image: `${WEBSITE_HOST_URL}/images/site-preview.png`, |
||||||
|
type: 'website', |
||||||
|
...customMeta, |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<NextHead> |
||||||
|
<title>{meta.title}</title> |
||||||
|
<meta content={meta.description} name="РоботТОП – это робототехнические соревнования, в которых могут принять участие молодые любители робототехники, объединившись в команды." /> |
||||||
|
<meta property="og:url" content={`${WEBSITE_HOST_URL}${router.asPath}`} /> |
||||||
|
<link rel="canonical" href={`${WEBSITE_HOST_URL}${router.asPath}`} /> |
||||||
|
<meta property="og:type" content={meta.type} /> |
||||||
|
<meta property="og:site_name" content="РоботТоп - робототехнический фестиваль" /> |
||||||
|
<meta property="og:description" content={meta.description} /> |
||||||
|
<meta property="og:title" content={meta.title} /> |
||||||
|
<meta property="og:image" content={meta.image} /> |
||||||
|
<meta name="twitter:card" content="summary_large_image" /> |
||||||
|
<meta name="twitter:title" content={meta.title} /> |
||||||
|
<meta name="twitter:description" content={meta.description} /> |
||||||
|
<meta name="twitter:image" content={meta.image} /> |
||||||
|
{meta.date && ( |
||||||
|
<meta property="article:published_time" content={meta.date} /> |
||||||
|
)} |
||||||
|
</NextHead> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Head; |
@ -0,0 +1,46 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { MetaProps } from '../types/layout'; |
||||||
|
import Head from './Head'; |
||||||
|
import Navigation from './Navigation'; |
||||||
|
import ThemeSwitch from './ThemeSwitch'; |
||||||
|
|
||||||
|
type LayoutProps = { |
||||||
|
children: React.ReactNode; |
||||||
|
customMeta?: MetaProps; |
||||||
|
}; |
||||||
|
|
||||||
|
export const WEBSITE_HOST_URL = 'https://robotop.krasnikov.pro/'; |
||||||
|
|
||||||
|
const Layout = ({ children, customMeta }: LayoutProps): JSX.Element => { |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<Head customMeta={customMeta} /> |
||||||
|
<header> |
||||||
|
<div className="max-w-5xl px-8 mx-auto max-w"> |
||||||
|
<div className="flex items-center justify-between py-6"> |
||||||
|
<Navigation /> |
||||||
|
<ThemeSwitch /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</header> |
||||||
|
<main> |
||||||
|
<div className="max-w-5xl px-8 py-4 mx-auto max-w"> |
||||||
|
{children} |
||||||
|
</div> |
||||||
|
</main> |
||||||
|
<footer className="py-8"> |
||||||
|
<div className="max-w-5xl px-8 mx-auto max-w"> |
||||||
|
Разработано {' '} |
||||||
|
<a |
||||||
|
className="text-gray-900 dark:text-white" |
||||||
|
href="https://krasnikov.pro" target='_blank' rel="noreferrer" |
||||||
|
> |
||||||
|
Krasnikov.pro - {(new Date()).getFullYear()} год |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
</footer> |
||||||
|
</> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Layout; |
@ -0,0 +1,52 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
type UserProps = { |
||||||
|
team_name: string; |
||||||
|
name_team_coach: string; |
||||||
|
training_institution_team: string; |
||||||
|
name_first_participant: string; |
||||||
|
name_second_participant: string; |
||||||
|
name_third_party: string; |
||||||
|
classTeam: string[]; |
||||||
|
}; |
||||||
|
|
||||||
|
export const LoadingTeamsForm : React.FC<UserProps> = ({ |
||||||
|
team_name, |
||||||
|
name_team_coach, |
||||||
|
training_institution_team, |
||||||
|
name_first_participant, |
||||||
|
name_second_participant, |
||||||
|
name_third_party, |
||||||
|
classTeam |
||||||
|
}) => { |
||||||
|
|
||||||
|
const flatten = (arr) => { |
||||||
|
const arrOfNum = []; |
||||||
|
arr.split(',').forEach(str => { |
||||||
|
arrOfNum.push(Number(str)); |
||||||
|
}); |
||||||
|
return Math.min.apply(null, arrOfNum.filter(Boolean)); //Math.min(...arrOfNum);
|
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> |
||||||
|
<th scope="row" className="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"> |
||||||
|
{team_name} |
||||||
|
</th> |
||||||
|
<td className="px-6 py-4"> |
||||||
|
{name_team_coach} |
||||||
|
</td> |
||||||
|
<td className="px-6 py-4"> |
||||||
|
{training_institution_team} |
||||||
|
</td> |
||||||
|
<td className="px-6 py-4"> |
||||||
|
{name_first_participant +', ' + name_second_participant + ', ' + name_third_party} |
||||||
|
</td> |
||||||
|
<td className="px-6 py-4"> |
||||||
|
{ flatten(classTeam)} |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default LoadingTeamsForm; |
@ -0,0 +1,91 @@ |
|||||||
|
import Link from 'next/link'; |
||||||
|
import React, { useState } from 'react'; |
||||||
|
import { MenuIcon, XIcon } from '@heroicons/react/outline' |
||||||
|
import { Transition } from "@headlessui/react"; |
||||||
|
import { useRouter } from 'next/router' |
||||||
|
|
||||||
|
const navigation = [ |
||||||
|
{ name: 'Главная', href: '/', as: false } |
||||||
|
] |
||||||
|
|
||||||
|
function classNames(...classes) { |
||||||
|
return classes.filter(Boolean).join(' ') |
||||||
|
} |
||||||
|
|
||||||
|
const Navigation = (): JSX.Element => { |
||||||
|
const [isOpen, setIsOpen] = useState(false); |
||||||
|
const router = useRouter(); |
||||||
|
|
||||||
|
return ( |
||||||
|
<nav className=""> |
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
||||||
|
<div className="flex items-center justify-between h-16"> |
||||||
|
<div className="flex items-center"> |
||||||
|
<div className="hidden md:block"> |
||||||
|
<div className="ml-10 flex items-baseline space-x-4"> |
||||||
|
{navigation.map((item) => ( |
||||||
|
<Link as={ item.as ? '/posts/'+item.as : ''} href={item.href} key={item.name}> |
||||||
|
<a |
||||||
|
className={classNames('bg-gray-900 text-white text-gray-900 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium' |
||||||
|
)} |
||||||
|
aria-current={false} |
||||||
|
> |
||||||
|
{item.name} |
||||||
|
</a> |
||||||
|
</Link> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="-mr-2 flex md:hidden"> |
||||||
|
<button |
||||||
|
onClick={() => setIsOpen(!isOpen)} |
||||||
|
type="button" |
||||||
|
aria-controls="mobile-menu" |
||||||
|
aria-expanded="false" |
||||||
|
> |
||||||
|
<span className="sr-only">Открыть главное меню</span> |
||||||
|
{!isOpen ? ( |
||||||
|
<MenuIcon className="block h-6 w-6" aria-hidden="false" /> |
||||||
|
) : ( |
||||||
|
<XIcon className="block h-6 w-6" aria-hidden="true" /> |
||||||
|
)} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<Transition |
||||||
|
show={isOpen} |
||||||
|
enter="transition ease-out duration-100 transform" |
||||||
|
enterFrom="opacity-0 scale-95" |
||||||
|
enterTo="opacity-100 scale-100" |
||||||
|
leave="transition ease-in duration-75 transform" |
||||||
|
leaveFrom="opacity-100 scale-100" |
||||||
|
leaveTo="opacity-0 scale-95" |
||||||
|
className="absolute bg-gray-100 z-50" |
||||||
|
> |
||||||
|
{(ref) => ( |
||||||
|
<div className="md:hidden" id="mobile-menu"> |
||||||
|
<div ref={ref} className="px-2 pt-2 pb-3 space-y-1 sm:px-3"> |
||||||
|
{navigation.map((item) => ( |
||||||
|
<Link as={'/posts/'+item.as} href={item.href} key={item.name}> |
||||||
|
<a |
||||||
|
className={classNames('text-gray-900 hover:bg-gray-900 hover:text-white', |
||||||
|
'block px-3 py-2 rounded-md text-base font-medium' |
||||||
|
)} |
||||||
|
aria-current={item.href ? 'page' : undefined} |
||||||
|
> |
||||||
|
{item.name} |
||||||
|
</a> |
||||||
|
</Link> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</Transition> |
||||||
|
</nav> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Navigation; |
@ -0,0 +1,136 @@ |
|||||||
|
import React,{useRef} from 'react'; |
||||||
|
import { useForm, SubmitHandler, FormProvider } from "react-hook-form"; |
||||||
|
import { Select, Input, Link } from "./UX"; |
||||||
|
|
||||||
|
interface IFormInputs { |
||||||
|
name_team_coach: string, |
||||||
|
coach_telefon_number: string, |
||||||
|
email_address: string, |
||||||
|
city_team: string, |
||||||
|
training_institution_team: string, |
||||||
|
team_name: string, |
||||||
|
name_first_participant: string, |
||||||
|
first_partial_class: number, |
||||||
|
name_second_participant: string, |
||||||
|
second_class: number, |
||||||
|
name_third_party: string, |
||||||
|
third_part_class: number, |
||||||
|
body?: string[] | number[] |
||||||
|
} |
||||||
|
|
||||||
|
const defaultValues = { |
||||||
|
name_team_coach: ``, |
||||||
|
coach_telefon_number: ``, |
||||||
|
email_address: '', |
||||||
|
city_team: '', |
||||||
|
training_institution_team: '', |
||||||
|
team_name: '', |
||||||
|
name_first_participant: '', |
||||||
|
first_partial_class: 0, |
||||||
|
name_second_participant: 'нет', |
||||||
|
second_class: 0, |
||||||
|
name_third_party: 'нет', |
||||||
|
third_part_class: 0, |
||||||
|
}; |
||||||
|
|
||||||
|
export const RegistrationForm = (props): JSX.Element => { |
||||||
|
const form = useRef(null); |
||||||
|
const methods = useForm({ defaultValues }); |
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<IFormInputs> = data => { |
||||||
|
fetch('/api/registration', { method: 'POST', body: Object.values(data) as any}) |
||||||
|
.then((data) => { |
||||||
|
props.updateData(data); |
||||||
|
}) |
||||||
|
methods.reset(defaultValues); |
||||||
|
} |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="mt-10 sm:mt-0"> |
||||||
|
<div className="md:grid md:grid-cols-3 md:gap-6"> |
||||||
|
<div className="md:col-span-1"> |
||||||
|
<div className="px-4 sm:px-0"> |
||||||
|
<h3 className="text-lg font-medium leading-6">Регистрация команды</h3> |
||||||
|
<p className="mt-1 text-sm">Введите актуальные данные команды</p> |
||||||
|
<p className="mt-1 text-sm">От каждого учебного заведения может быть зарегистрированно неограниченое количеставо команд</p> |
||||||
|
<p className="mt-1 text-sm"> Подписывайтесь на наш |
||||||
|
<Link href="https://t.me/robotop_competition"> Telegram канал</Link> |
||||||
|
, что-бы быть в курсе новостей про соревнование </p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="mt-5 md:mt-0 md:col-span-2"> |
||||||
|
<FormProvider {...methods} > |
||||||
|
<form ref={form} onSubmit={methods.handleSubmit(onSubmit)}> |
||||||
|
<div className="shadow overflow-hidden sm:rounded-md"> |
||||||
|
<div className="px-4 py-5 bg-white sm:p-6"> |
||||||
|
<div className="grid grid-cols-6 gap-6"> |
||||||
|
|
||||||
|
<div className="col-span-6 sm:col-span-3"> |
||||||
|
<Input placeholder="Иванов Иван Иванович" name="name_team_coach" text="Введите ФИО тренера" additional={""}/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-6 sm:col-span-3"> |
||||||
|
<Input placeholder="+79181234567" name="coach_telefon_number" text="Номер телефона тренера" additional={"valueAsNumber: true"} /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-6 sm:col-span-3"> |
||||||
|
<Input placeholder="you@example.com" name="email_address" text="Email тренера" additional={`pattern: /^(([^<>()[]\\.,;:s@"]+(.[^<>()[]\\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/ })`} /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-6 sm:col-span-3"> |
||||||
|
<Input placeholder="Краснодар" name="city_team" text="Город команда" additional={""} /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-3"> |
||||||
|
<Input placeholder="МАОУ СОШ 103" name="training_institution_team" text="Учебное заведение команды" additional={""} /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-3"> |
||||||
|
<Input placeholder="Фиксики" name="team_name" text="Название команды" additional={""} /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-6 sm:col-span-3"> |
||||||
|
<Input placeholder="Иванов Иван Иванович" name="name_first_participant" text="ФИО первого участника" additional={""} /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-6 sm:col-span-3"> |
||||||
|
<Select text={'Класс участника'} name={'first_partial_class'}/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-6 sm:col-span-3"> |
||||||
|
<Input placeholder="Иванов Петр Иванович / нет" name="name_second_participant" text="ФИО второго участника" additional={""} /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-6 sm:col-span-3"> |
||||||
|
<Select text={'Класс участника'} name={'second_class'}/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-6 sm:col-span-3"> |
||||||
|
<Input placeholder="Иванов Дмитрий Иванович / нет" name="name_third_party" text="ФИО третьего участника" additional={""}/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="col-span-6 sm:col-span-3"> |
||||||
|
<Select text={'Класс участника'} name={'third_part_class'}/> |
||||||
|
</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="px-4 py-3 bg-gray-50 text-right sm:px-6"> |
||||||
|
<button |
||||||
|
type="submit" |
||||||
|
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" |
||||||
|
> |
||||||
|
Зарегистрировать команду |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</FormProvider> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default RegistrationForm; |
@ -0,0 +1,102 @@ |
|||||||
|
import { useTheme } from 'next-themes'; |
||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
|
||||||
|
const ThemeSwitch = (): JSX.Element => { |
||||||
|
const [mounted, setMounted] = React.useState(false); |
||||||
|
const { theme, setTheme } = useTheme(); |
||||||
|
|
||||||
|
// After mounting, we have access to the theme
|
||||||
|
React.useEffect(() => setMounted(true), []); |
||||||
|
|
||||||
|
if (!mounted) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
const isDark = theme === 'dark'; |
||||||
|
const color = isDark ? '#fff' : '#000'; |
||||||
|
const maskColor = isDark ? '#000' : '#fff'; |
||||||
|
return ( |
||||||
|
<button |
||||||
|
className="theme-button" |
||||||
|
type="button" |
||||||
|
aria-label="Toggle Dark Mode" |
||||||
|
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} |
||||||
|
> |
||||||
|
<div className="moon-or-sun" /> |
||||||
|
<div className="moon-mask" /> |
||||||
|
<style jsx>{` |
||||||
|
.theme-button { |
||||||
|
opacity: 0.5; |
||||||
|
position: relative; |
||||||
|
border-radius: 5px; |
||||||
|
width: 42px; |
||||||
|
height: 42px; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
transition: opacity 0.3s ease; |
||||||
|
} |
||||||
|
.theme-button:hover { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
.moon-or-sun { |
||||||
|
position: relative; |
||||||
|
width: 20px; |
||||||
|
height: 20px; |
||||||
|
border-radius: 50%; |
||||||
|
border: ${isDark ? '4px' : '2px'} solid; |
||||||
|
border-color: ${color}; |
||||||
|
background: ${color}; |
||||||
|
transform: scale(${isDark ? 0.5 : 1}); |
||||||
|
transition: all 0.45s ease; |
||||||
|
overflow: ${isDark ? 'visible' : 'hidden'}; |
||||||
|
} |
||||||
|
.moon-or-sun::before { |
||||||
|
content: ''; |
||||||
|
position: absolute; |
||||||
|
right: -9px; |
||||||
|
top: -9px; |
||||||
|
height: 20px; |
||||||
|
width: 20px; |
||||||
|
border: 2px solid; |
||||||
|
border-color: ${color}; |
||||||
|
border-radius: 50%; |
||||||
|
transform: translate(${isDark ? '14px, -14px' : '0, 0'}); |
||||||
|
opacity: ${isDark ? 0 : 1}; |
||||||
|
transition: transform 0.45s ease; |
||||||
|
} |
||||||
|
.moon-or-sun::after { |
||||||
|
content: ''; |
||||||
|
width: 8px; |
||||||
|
height: 8px; |
||||||
|
border-radius: 50%; |
||||||
|
margin: -4px 0 0 -4px; |
||||||
|
position: absolute; |
||||||
|
top: 50%; |
||||||
|
left: 50%; |
||||||
|
box-shadow: 0 -23px 0 ${color}, 0 23px 0 ${color}, 23px 0 0 ${color}, |
||||||
|
-23px 0 0 ${color}, 15px 15px 0 ${color}, -15px 15px 0 ${color}, |
||||||
|
15px -15px 0 ${color}, -15px -15px 0 ${color}; |
||||||
|
transform: scale(${isDark ? 1 : 0}); |
||||||
|
transition: all 0.35s ease; |
||||||
|
} |
||||||
|
.moon-mask { |
||||||
|
position: absolute; |
||||||
|
right: 4px; |
||||||
|
top: 4px; |
||||||
|
height: 20px; |
||||||
|
width: 20px; |
||||||
|
border-radius: 50%; |
||||||
|
border: 0; |
||||||
|
background: ${maskColor}; |
||||||
|
transform: translate(${isDark ? '4px, -4px' : '0, 0'}); |
||||||
|
opacity: ${isDark ? 0 : 1}; |
||||||
|
transition: transform 0.45s ease; |
||||||
|
} |
||||||
|
`}</style>
|
||||||
|
</button> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default ThemeSwitch; |
@ -0,0 +1,7 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
export const Alert = (text) => { |
||||||
|
return( |
||||||
|
<p className="mt-2 text-sm text-red-600 dark:text-red-500 font-medium">{text}</p> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { useFormContext } from "react-hook-form"; |
||||||
|
|
||||||
|
type Props = { |
||||||
|
text: string; |
||||||
|
name: string; |
||||||
|
placeholder: string; |
||||||
|
additional: string; |
||||||
|
} |
||||||
|
|
||||||
|
export const Input: React.FC<Props> = ({text, name, placeholder, additional}) => { |
||||||
|
const { register } = useFormContext(); |
||||||
|
const options = {required: true, maxLength: 80, additional}; |
||||||
|
return( |
||||||
|
<> |
||||||
|
<label htmlFor={name} className="block text-sm font-medium text-gray-700"> |
||||||
|
{text} |
||||||
|
</label> |
||||||
|
<input |
||||||
|
{...register(name, options )} |
||||||
|
name={name} |
||||||
|
placeholder={placeholder} |
||||||
|
className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" |
||||||
|
/> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
type Props = { |
||||||
|
children: string; |
||||||
|
href: string; |
||||||
|
} |
||||||
|
|
||||||
|
export const Link: React.FC<Props> = ({children, href}) => { |
||||||
|
return( |
||||||
|
<> |
||||||
|
<a href={href} target='_blank' rel="noreferrer" className="dark:text-white">{children}</a> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
import React from 'react'; |
||||||
|
//import { useFormContext } from "react-hook-form";
|
||||||
|
import { useFormContext } from "react-hook-form"; |
||||||
|
|
||||||
|
type Props = { |
||||||
|
text: string; |
||||||
|
name: string; |
||||||
|
children?: JSX.Element[] | JSX.Element; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export const Select: React.FC<Props> = ({text, name}) => { |
||||||
|
const { register } = useFormContext(); |
||||||
|
return( |
||||||
|
<> |
||||||
|
<label htmlFor="country" className="block text-sm font-medium text-gray-700"> |
||||||
|
{text} |
||||||
|
</label> |
||||||
|
<select |
||||||
|
{...register(name)} // ...register("first_partial_class")
|
||||||
|
name={name} |
||||||
|
defaultValue={0} |
||||||
|
className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" |
||||||
|
> |
||||||
|
<option value={0}>-- Выбрать --</option> |
||||||
|
<option >1</option> |
||||||
|
<option >2</option> |
||||||
|
<option >3</option> |
||||||
|
<option >4</option> |
||||||
|
<option >5</option> |
||||||
|
<option >6</option> |
||||||
|
<option >7</option> |
||||||
|
<option >8</option> |
||||||
|
<option >9</option> |
||||||
|
<option >10</option> |
||||||
|
<option >11</option> |
||||||
|
</select> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
export * from './Select' |
||||||
|
export * from './Alert' |
||||||
|
export * from './Input' |
||||||
|
export * from './Link' |
@ -0,0 +1,14 @@ |
|||||||
|
export interface VideoProps { |
||||||
|
title: string; |
||||||
|
src: string; |
||||||
|
} |
||||||
|
|
||||||
|
export function Video(props: VideoProps) { |
||||||
|
return ( |
||||||
|
<video controls style={{ width: '960px' }}> |
||||||
|
<source src={props.src} /> |
||||||
|
</video> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default Video; |
@ -0,0 +1,6 @@ |
|||||||
|
// .env.local |
||||||
|
|
||||||
|
USER_="Имя пользователя базы данных" |
||||||
|
HOST="IP адрес базы данных" |
||||||
|
DATABASE="Имя базы данных" |
||||||
|
PASSWORD="Пароль доступа к базе данных" |
@ -0,0 +1,17 @@ |
|||||||
|
module.exports = { |
||||||
|
roots: ['<rootDir>'], |
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'jsx'], |
||||||
|
testPathIgnorePatterns: ['<rootDir>[/\\\\](node_modules|.next)[/\\\\]'], |
||||||
|
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$'], |
||||||
|
transform: { |
||||||
|
'^.+\\.(ts|tsx)$': 'babel-jest', |
||||||
|
}, |
||||||
|
watchPlugins: [ |
||||||
|
'jest-watch-typeahead/filename', |
||||||
|
'jest-watch-typeahead/testname', |
||||||
|
], |
||||||
|
moduleNameMapper: { |
||||||
|
'\\.(css|less|sass|scss)$': 'identity-obj-proxy', |
||||||
|
'\\.(gif|ttf|eot|svg|png)$': '<rootDir>/test/__mocks__/fileMock.js', |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,45 @@ |
|||||||
|
import fs from 'fs'; |
||||||
|
import matter from 'gray-matter'; |
||||||
|
import { join } from 'path'; |
||||||
|
import { POSTS_PATH } from '../utils/mdxUtils'; |
||||||
|
|
||||||
|
export function getPostSlugs(): string[] { |
||||||
|
return fs.readdirSync(POSTS_PATH); |
||||||
|
} |
||||||
|
|
||||||
|
type PostItems = { |
||||||
|
[key: string]: string; |
||||||
|
}; |
||||||
|
|
||||||
|
export function getPostBySlug(slug: string, fields: string[] = []): PostItems { |
||||||
|
const realSlug = slug.replace(/\.mdx$/, ''); |
||||||
|
const fullPath = join(POSTS_PATH, `${realSlug}.mdx`); |
||||||
|
const fileContents = fs.readFileSync(fullPath, 'utf8'); |
||||||
|
const { data, content } = matter(fileContents); |
||||||
|
|
||||||
|
const items: PostItems = {}; |
||||||
|
|
||||||
|
// Ensure only the minimal needed data is exposed
|
||||||
|
fields.forEach((field) => { |
||||||
|
if (field === 'slug') { |
||||||
|
items[field] = realSlug; |
||||||
|
} |
||||||
|
if (field === 'content') { |
||||||
|
items[field] = content; |
||||||
|
} |
||||||
|
if (data[field]) { |
||||||
|
items[field] = data[field]; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return items; |
||||||
|
} |
||||||
|
|
||||||
|
export function getAllPosts(fields: string[] = []): PostItems[] { |
||||||
|
const slugs = getPostSlugs(); |
||||||
|
const posts = slugs |
||||||
|
.map((slug) => getPostBySlug(slug, fields)) |
||||||
|
// sort posts by date in descending order
|
||||||
|
.sort((post1, post2) => (post1.date > post2.date ? -1 : 1)); |
||||||
|
return posts; |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
-- phpMyAdmin SQL Dump |
||||||
|
-- version 4.9.7 |
||||||
|
-- https://www.phpmyadmin.net/ |
||||||
|
|
||||||
|
-- -------------------------------------------------------- |
||||||
|
|
||||||
|
-- |
||||||
|
-- Table structure for table `members` |
||||||
|
-- |
||||||
|
-- Creation: Jun 14, 2022 at 04:25 AM |
||||||
|
-- Last update: Jul 15, 2022 at 10:08 PM |
||||||
|
-- |
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `members`; |
||||||
|
CREATE TABLE `members` ( |
||||||
|
`id` int(5) NOT NULL, |
||||||
|
`name_team_coach` varchar(100) NOT NULL, |
||||||
|
`coach_telefon_number` varchar(100) NOT NULL, |
||||||
|
`trainer_mail` varchar(100) NOT NULL, |
||||||
|
`city_team` varchar(100) NOT NULL, |
||||||
|
`training_institution_team` varchar(100) NOT NULL, |
||||||
|
`team_name` varchar(100) NOT NULL, |
||||||
|
`name_first_participant` varchar(100) NOT NULL, |
||||||
|
`first_partial_class` int(2) NOT NULL, |
||||||
|
`name_second_participant` varchar(100) NOT NULL, |
||||||
|
`second_class` int(2) NOT NULL, |
||||||
|
`name_third_party` varchar(100) NOT NULL, |
||||||
|
`third_part_class` int(2) NOT NULL, |
||||||
|
`reg_time_add` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, |
||||||
|
`status` int(1) NOT NULL DEFAULT '1' |
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
||||||
|
|
||||||
|
-- |
||||||
|
-- Dumping data for table `members` |
||||||
|
-- |
||||||
|
|
||||||
|
INSERT INTO `members` (`id`, `name_team_coach`, `coach_telefon_number`, `trainer_mail`, `city_team`, `training_institution_team`, `team_name`, `name_first_participant`, `first_partial_class`, `name_second_participant`, `second_class`, `name_third_party`, `third_part_class`, `reg_time_add`, `status`) VALUES |
||||||
|
(36, 'Красников Павел Геннадьевич', '79189458044', 'crapsh@gmail.com', 'Краснодар', 'МАОУ СОШ 103', 'Фиксики', 'Иван Филлипов', 8, 'Владислав Дельнов', 8, 'нет', 0, '2022-07-02 18:34:33', 1); |
||||||
|
|
||||||
|
-- |
||||||
|
-- Indexes for dumped tables |
||||||
|
-- |
||||||
|
|
||||||
|
-- |
||||||
|
-- Indexes for table `members` |
||||||
|
-- |
||||||
|
ALTER TABLE `members` |
||||||
|
ADD PRIMARY KEY (`id`); |
||||||
|
|
||||||
|
-- |
||||||
|
-- AUTO_INCREMENT for dumped tables |
||||||
|
-- |
||||||
|
|
||||||
|
-- |
||||||
|
-- AUTO_INCREMENT for table `members` |
||||||
|
-- |
||||||
|
ALTER TABLE `members` |
||||||
|
MODIFY `id` int(5) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=98; |
||||||
|
COMMIT; |
||||||
|
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; |
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; |
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; |
@ -0,0 +1,5 @@ |
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
@ -0,0 +1,17 @@ |
|||||||
|
import nextMDX from '@next/mdx' |
||||||
|
import remarkGfm from 'remark-gfm' |
||||||
|
import remarkParse from 'remark-parse' |
||||||
|
import remarkRehype from 'remark-rehype' |
||||||
|
import rehypeStringify from 'rehype-stringify' |
||||||
|
|
||||||
|
const withMDX = nextMDX({ |
||||||
|
extension: /\.mdx?$/, |
||||||
|
options: { |
||||||
|
remarkPlugins: [remarkGfm, remarkParse, remarkRehype], |
||||||
|
rehypePlugins: [rehypeStringify], |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
export default withMDX({ |
||||||
|
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'], |
||||||
|
}) |
@ -0,0 +1,79 @@ |
|||||||
|
{ |
||||||
|
"name": "nextjs-typescript-mdx-blog", |
||||||
|
"version": "1.0.0", |
||||||
|
"scripts": { |
||||||
|
"dev": "next dev", |
||||||
|
"build": "next build", |
||||||
|
"start": "next start -p 3005", |
||||||
|
"type-check": "tsc --pretty --noEmit", |
||||||
|
"format": "prettier --write .", |
||||||
|
"lint": "eslint . --ext ts --ext tsx --ext js", |
||||||
|
"test": "jest", |
||||||
|
"test-all": "yarn lint && yarn type-check && yarn test" |
||||||
|
}, |
||||||
|
"husky": { |
||||||
|
"hooks": { |
||||||
|
"pre-commit": "lint-staged", |
||||||
|
"pre-push": "yarn run type-check" |
||||||
|
} |
||||||
|
}, |
||||||
|
"lint-staged": { |
||||||
|
"*.@(ts|tsx)": [ |
||||||
|
"yarn lint", |
||||||
|
"yarn format" |
||||||
|
] |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@headlessui/react": "^1.6.5", |
||||||
|
"@heroicons/react": "^1.0.6", |
||||||
|
"@mux/mux-player": "^1.6.0", |
||||||
|
"@mux/mux-player-react": "^1.6.0", |
||||||
|
"@next/mdx": "^12.2.0", |
||||||
|
"@reduxjs/toolkit": "^1.8.3", |
||||||
|
"@tailwindcss/typography": "^0.5.2", |
||||||
|
"@types/node-fetch": "^2.6.2", |
||||||
|
"axios": "^0.27.2", |
||||||
|
"date-fns": "^2.28.0", |
||||||
|
"gray-matter": "^4.0.3", |
||||||
|
"mysql2": "^2.3.3", |
||||||
|
"next": "^12.2.0", |
||||||
|
"next-mdx-remote": "^4.0.3", |
||||||
|
"next-themes": "^0.2.0", |
||||||
|
"next-videos": "^1.4.1", |
||||||
|
"react": "^18.2.0", |
||||||
|
"react-confirm-alert": "^3.0.2", |
||||||
|
"react-dom": "^18.2.0", |
||||||
|
"react-hook-form": "^7.33.1", |
||||||
|
"react-redux": "^8.0.2", |
||||||
|
"react-toastify": "^9.0.5", |
||||||
|
"react-yandex-metrika": "^2.6.0", |
||||||
|
"rehype-autolink-headings": "^6.1.1", |
||||||
|
"rehype-slug": "^5.0.1", |
||||||
|
"remark-code-titles": "^0.1.2", |
||||||
|
"remark-gfm": "^3.0.1" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@testing-library/react": "^13.3.0", |
||||||
|
"@types/gtag.js": "^0.0.10", |
||||||
|
"@types/jest": "^28.1.4", |
||||||
|
"@types/node": "^18.0.0", |
||||||
|
"@types/react": "^18.0.14", |
||||||
|
"@typescript-eslint/eslint-plugin": "^5.30.3", |
||||||
|
"autoprefixer": "^10.4.7", |
||||||
|
"babel-jest": "^28.1.2", |
||||||
|
"eslint": "^8.19.0", |
||||||
|
"eslint-config-next": "^12.2.0", |
||||||
|
"eslint-config-prettier": "^8.5.0", |
||||||
|
"eslint-plugin-react": "^7.30.1", |
||||||
|
"husky": "^8.0.1", |
||||||
|
"identity-obj-proxy": "^3.0.0", |
||||||
|
"jest": "^28.1.2", |
||||||
|
"jest-watch-typeahead": "^1.1.0", |
||||||
|
"lint-staged": "^13.0.3", |
||||||
|
"postcss": "^8.4.14", |
||||||
|
"prettier": "^2.7.1", |
||||||
|
"rehype": "^12.0.1", |
||||||
|
"tailwindcss": "^3.1.4", |
||||||
|
"typescript": "^4.7.4" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
import { ThemeProvider } from 'next-themes'; |
||||||
|
import type { AppProps } from 'next/app'; |
||||||
|
import React from 'react'; |
||||||
|
import { Provider } from 'react-redux'; |
||||||
|
import '../styles/globals.css'; |
||||||
|
import { store } from '../redux/store'; |
||||||
|
|
||||||
|
const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => { |
||||||
|
return ( |
||||||
|
<ThemeProvider attribute="class" enableSystem={false} defaultTheme="light"> |
||||||
|
<Provider store={store}> |
||||||
|
<Component {...pageProps} /> |
||||||
|
</Provider> |
||||||
|
</ThemeProvider> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default MyApp; |
@ -0,0 +1,30 @@ |
|||||||
|
import Document, { Head, Html, Main, NextScript } from 'next/document'; |
||||||
|
class MyDocument extends Document { |
||||||
|
render(): JSX.Element { |
||||||
|
return ( |
||||||
|
<Html lang="ru"> |
||||||
|
<Head /> |
||||||
|
<body className="bg-white dark:bg-black text-gray-900 dark:text-white"> |
||||||
|
<script type="text/javascript" |
||||||
|
dangerouslySetInnerHTML={{ |
||||||
|
__html: ` |
||||||
|
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)}; |
||||||
|
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)}) |
||||||
|
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym"); |
||||||
|
ym(89626868, "init", { |
||||||
|
clickmap:true, |
||||||
|
trackLinks:true, |
||||||
|
accurateTrackBounce:true |
||||||
|
}); |
||||||
|
`,
|
||||||
|
}} |
||||||
|
/> |
||||||
|
<Main /> |
||||||
|
<NextScript /> |
||||||
|
</body> |
||||||
|
</Html> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default MyDocument; |
@ -0,0 +1,20 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import Layout from '../components/Layout'; |
||||||
|
|
||||||
|
export const About = (): JSX.Element => { |
||||||
|
return ( |
||||||
|
<Layout |
||||||
|
customMeta={{ |
||||||
|
title: 'О нас', |
||||||
|
}} |
||||||
|
> |
||||||
|
<h2>RobotTop</h2> |
||||||
|
<p>Организатор соревнований лаборатория робототехники Krasnikov Robotics</p> |
||||||
|
<p>Занимаемся робототехникой с 2006 года</p> |
||||||
|
<p>Наши ученики участники так соревнований как EUROBOT, Робофест, FLL, РТК</p> |
||||||
|
<p>В 2022 году проводим первый Краевой робототехнический фестиваль на базе МАОУ СОШ № 103 </p> |
||||||
|
</Layout> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default About; |
@ -0,0 +1,23 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import Layout from '../components/Layout'; |
||||||
|
import { Link } from '../components/UX'; |
||||||
|
|
||||||
|
export const About = (): JSX.Element => { |
||||||
|
return ( |
||||||
|
<Layout |
||||||
|
customMeta={{ |
||||||
|
title: 'Контакты', |
||||||
|
}} |
||||||
|
> |
||||||
|
<h1>РоботТоп</h1> |
||||||
|
<p><b>Организатор соревнований</b> <Link href="https://school103.centerstart.ru/sveden/common" >МАОУ СОШ 103 г. Краснодар</Link> </p> |
||||||
|
<p><b>Главный судья соревнований</b><Link href="https://krasnikov.pro"> Красников Павел Геннадьевич </Link> - |
||||||
|
<Link href="tel:+7-918-945-80-44"> 8-918-945-80-44 </Link> -
|
||||||
|
<Link href="https://t.me/krasnikovPavel"> Telegram</Link> |
||||||
|
</p> |
||||||
|
<p><b>Вопросы по соревнованиям можно задавать в </b><Link href="https://t.me/robotop_competition"> Telegram группе </Link></p> |
||||||
|
</Layout> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default About; |
@ -0,0 +1,52 @@ |
|||||||
|
import { format, parseISO } from 'date-fns'; |
||||||
|
import { GetStaticProps } from 'next'; |
||||||
|
import Link from 'next/link'; |
||||||
|
import React from 'react'; |
||||||
|
import Layout from '../components/Layout'; |
||||||
|
import { getAllPosts } from '../lib/api'; |
||||||
|
import { PostType } from '../types/post'; |
||||||
|
|
||||||
|
type IndexProps = { |
||||||
|
posts: PostType[]; |
||||||
|
}; |
||||||
|
|
||||||
|
export const Index = ({ posts }: IndexProps): JSX.Element => { |
||||||
|
return ( |
||||||
|
<Layout> |
||||||
|
<h1>Информатика</h1> |
||||||
|
{posts.map((post) => ( |
||||||
|
<article key={post.slug} className="mt-12"> |
||||||
|
<p className="mb-1 text-sm text-gray-500 dark:text-gray-400"> |
||||||
|
{format(parseISO(post.date), 'MMMM dd, yyyy')} |
||||||
|
</p> |
||||||
|
<h1 className="mb-2 text-xl"> |
||||||
|
<Link as={`/posts/${post.slug}`} href={`/posts/[slug]`}> |
||||||
|
<a className="text-gray-900 dark:text-white dark:hover:text-blue-400"> |
||||||
|
{post.title} |
||||||
|
</a> |
||||||
|
</Link> |
||||||
|
</h1> |
||||||
|
<p className="mb-3">{post.description}</p> |
||||||
|
<p> |
||||||
|
<Link as={`/posts/${post.slug}`} href={`/posts/[slug]`}> |
||||||
|
<a>Подробнее...</a> |
||||||
|
</Link> |
||||||
|
</p> |
||||||
|
</article> |
||||||
|
))} |
||||||
|
<div> |
||||||
|
</div> |
||||||
|
|
||||||
|
</Layout> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps = async () => { |
||||||
|
const posts = getAllPosts(['date', 'description', 'slug', 'title']); |
||||||
|
|
||||||
|
return { |
||||||
|
props: { posts }, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export default Index; |
@ -0,0 +1,98 @@ |
|||||||
|
import { format, parseISO } from 'date-fns'; |
||||||
|
import fs from 'fs'; |
||||||
|
import matter from 'gray-matter'; |
||||||
|
import { GetStaticPaths, GetStaticProps } from 'next'; |
||||||
|
import { serialize } from 'next-mdx-remote/serialize'; |
||||||
|
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'; |
||||||
|
import Head from 'next/head'; |
||||||
|
import Image from 'next/image'; |
||||||
|
import Link from 'next/link'; |
||||||
|
import path from 'path'; |
||||||
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings'; |
||||||
|
import rehypeSlug from 'rehype-slug'; |
||||||
|
import Layout, { WEBSITE_HOST_URL } from '../../components/Layout'; |
||||||
|
import { MetaProps } from '../../types/layout'; |
||||||
|
import { PostType } from '../../types/post'; |
||||||
|
import { postFilePaths, POSTS_PATH } from '../../utils/mdxUtils'; |
||||||
|
import { Video } from '../../components/Video' |
||||||
|
|
||||||
|
// Custom components/renderers to pass to MDX.
|
||||||
|
// Since the MDX files aren't loaded by webpack, they have no knowledge of how
|
||||||
|
// to handle import statements. Instead, you must include components in scope
|
||||||
|
// here.
|
||||||
|
const components = { |
||||||
|
Head, |
||||||
|
Image, |
||||||
|
Link, |
||||||
|
Video |
||||||
|
}; |
||||||
|
|
||||||
|
type PostPageProps = { |
||||||
|
source: MDXRemoteSerializeResult; |
||||||
|
frontMatter: PostType; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const PostPage = ({ source, frontMatter }: PostPageProps): JSX.Element => { |
||||||
|
const customMeta: MetaProps = { |
||||||
|
title: `${frontMatter.title} - RoboTop`, |
||||||
|
description: frontMatter.description, |
||||||
|
image: `${WEBSITE_HOST_URL}${frontMatter.image}`, |
||||||
|
date: frontMatter.date, |
||||||
|
type: 'article', |
||||||
|
}; |
||||||
|
return ( |
||||||
|
<Layout customMeta={customMeta}> |
||||||
|
<article className="max-w"> |
||||||
|
<h1 className="mb-3 text-gray-900 dark:text-white"> |
||||||
|
{frontMatter.title} |
||||||
|
</h1> |
||||||
|
<p className="mb-10 text-sm text-gray-500 dark:text-gray-400"> |
||||||
|
{format(parseISO(frontMatter.date), 'MMMM dd, yyyy')} |
||||||
|
</p> |
||||||
|
<div className="prose dark:prose-dark"> |
||||||
|
<MDXRemote {...source} components={components} /> |
||||||
|
</div> |
||||||
|
</article> |
||||||
|
</Layout> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps = async ({ params }) => { |
||||||
|
const postFilePath = path.join(POSTS_PATH, `${params.slug}.mdx`); |
||||||
|
const source = fs.readFileSync(postFilePath); |
||||||
|
|
||||||
|
const { content, data } = matter(source); |
||||||
|
|
||||||
|
const mdxSource = await serialize(content, { |
||||||
|
// Optionally pass remark/rehype plugins
|
||||||
|
mdxOptions: { |
||||||
|
remarkPlugins: [require('remark-code-titles')], |
||||||
|
rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings], |
||||||
|
}, |
||||||
|
scope: data, |
||||||
|
}); |
||||||
|
|
||||||
|
return { |
||||||
|
props: { |
||||||
|
source: mdxSource, |
||||||
|
frontMatter: data, |
||||||
|
}, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export const getStaticPaths: GetStaticPaths = async () => { |
||||||
|
const paths = postFilePaths |
||||||
|
// Remove file extensions for page paths
|
||||||
|
.map((path) => path.replace(/\.mdx?$/, '')) |
||||||
|
// Map the path into the static paths object required by Next.js
|
||||||
|
.map((slug) => ({ params: { slug } })); |
||||||
|
|
||||||
|
return { |
||||||
|
paths, |
||||||
|
fallback: false, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export default PostPage; |
@ -0,0 +1,8 @@ |
|||||||
|
// If you want to use other PostCSS plugins, see the following:
|
||||||
|
// https://tailwindcss.com/docs/using-with-preprocessors
|
||||||
|
module.exports = { |
||||||
|
plugins: { |
||||||
|
tailwindcss: { config: './tailwind.config.js' }, |
||||||
|
autoprefixer: {}, |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,57 @@ |
|||||||
|
--- |
||||||
|
title: Практическая работа - списки |
||||||
|
description: Создание списков и оглавления |
||||||
|
date: '2023-02-01' |
||||||
|
--- |
||||||
|
|
||||||
|
* Вам необходимо создать заголовок документа и три списка **Маркированный**, **Нумерованный**, **Многоуровневый**. |
||||||
|
* Ниже приведен пример как должен выглядеть документ и его содержание. |
||||||
|
|
||||||
|
<Image |
||||||
|
alt={`Создание списков и оглавления`} |
||||||
|
src={`/informatica/4.5_lists/exercise.png`} |
||||||
|
width={1073} |
||||||
|
height={489} |
||||||
|
priority |
||||||
|
/> |
||||||
|
|
||||||
|
* Текст |
||||||
|
> Маркированные и многоуровневые списки |
||||||
|
Программа MS Word имеет в своем функционале следующие три списка: |
||||||
|
Маркированный |
||||||
|
Нумерованный |
||||||
|
Многоуровневый |
||||||
|
Состав системного блока: |
||||||
|
Материнская плата |
||||||
|
Процессор |
||||||
|
Видеокарта |
||||||
|
Оперативная память |
||||||
|
Жесткий диск и SSD |
||||||
|
Привод |
||||||
|
Охлаждение |
||||||
|
Блок питания |
||||||
|
Процессоры Intel: |
||||||
|
Intel Core i9-13900KS |
||||||
|
Intel Xeon Platinum 8380 |
||||||
|
Intel Core i9-13900K |
||||||
|
Intel Core i9-13900KF |
||||||
|
Intel Core i9-13900F |
||||||
|
Intel Xeon Platinum 8358 |
||||||
|
Intel Xeon W-3375 |
||||||
|
Intel Xeon Gold 6348 |
||||||
|
Socket процессора: |
||||||
|
LGA1150 |
||||||
|
Intel Core i7-4790K |
||||||
|
Intel Xeon E3-1285 v4 |
||||||
|
Intel Xeon E3-1285L v4 |
||||||
|
LGA1200 |
||||||
|
Intel Xeon W-1390P |
||||||
|
Intel Xeon W-1370P |
||||||
|
Intel Core i9-11900K |
||||||
|
|
||||||
|
<Video |
||||||
|
src={`/informatica/4.5_lists/word_lists.mp4`} |
||||||
|
/> |
||||||
|
|
||||||
|
|
||||||
|
[На главную](/) |
@ -0,0 +1,15 @@ |
|||||||
|
import { configureStore } from '@reduxjs/toolkit'; |
||||||
|
import userSlice from './user/slice'; |
||||||
|
|
||||||
|
import { useDispatch } from 'react-redux'; |
||||||
|
|
||||||
|
export const store = configureStore({ |
||||||
|
reducer: {userSlice}, |
||||||
|
}) |
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof store.getState>; |
||||||
|
|
||||||
|
type AppDispatch = typeof store.dispatch; |
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
export const useAppDispatch = () => useDispatch<AppDispatch>(); |
@ -0,0 +1,13 @@ |
|||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit'; |
||||||
|
import axios from 'axios'; |
||||||
|
import { User } from './types'; |
||||||
|
|
||||||
|
export const fetchUser = createAsyncThunk( |
||||||
|
'User/fetchUserStatus', |
||||||
|
async () => { |
||||||
|
const { data } = await axios.get<User[]>(`/api/loadingLegisteredCommands`); |
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
//console.log(data);
|
||||||
|
return data; |
||||||
|
}, |
||||||
|
); |
@ -0,0 +1,4 @@ |
|||||||
|
export * from './selectors'; |
||||||
|
export * from './asyncActions'; |
||||||
|
export * from './slice'; |
||||||
|
export * from './types'; |
@ -0,0 +1,4 @@ |
|||||||
|
import { RootState } from '../store'; |
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
export const selectUserData = (state: RootState) => state.userSlice; |
@ -0,0 +1,39 @@ |
|||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; |
||||||
|
import { fetchUser } from './asyncActions'; |
||||||
|
import { User, Status, UserSliceState } from './types'; |
||||||
|
|
||||||
|
const initialState: UserSliceState = { |
||||||
|
user_items: [], |
||||||
|
user_status: Status.LOADING, // loading | success | error
|
||||||
|
}; |
||||||
|
|
||||||
|
const userSlice = createSlice({ |
||||||
|
name: 'categories', |
||||||
|
initialState, |
||||||
|
reducers: { |
||||||
|
setUser(user_status, action: PayloadAction<User[]>) { |
||||||
|
user_status.user_items = action.payload; |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
extraReducers: (builder) => { |
||||||
|
builder.addCase(fetchUser.pending, (user_status) => { |
||||||
|
user_status.user_status = Status.LOADING; |
||||||
|
user_status.user_items = []; |
||||||
|
}); |
||||||
|
|
||||||
|
builder.addCase(fetchUser.fulfilled, (user_status, action) => { |
||||||
|
user_status.user_status = Status.SUCCESS; |
||||||
|
user_status.user_items = action.payload; |
||||||
|
}); |
||||||
|
|
||||||
|
builder.addCase(fetchUser.rejected, (user_status) => { |
||||||
|
user_status.user_status = Status.ERROR; |
||||||
|
user_status.user_items = []; |
||||||
|
}); |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
export const { setUser } = userSlice.actions; |
||||||
|
|
||||||
|
export default userSlice.reducer; |
@ -0,0 +1,21 @@ |
|||||||
|
export type User = { |
||||||
|
team_name: string; |
||||||
|
name_team_coach: string; |
||||||
|
training_institution_team: string; |
||||||
|
name_first_participant: string; |
||||||
|
name_second_participant: string; |
||||||
|
name_third_party: string; |
||||||
|
classTeam: string[]; |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
export enum Status { |
||||||
|
LOADING = 'loading', |
||||||
|
SUCCESS = 'completed', |
||||||
|
ERROR = 'error', |
||||||
|
} |
||||||
|
|
||||||
|
export interface UserSliceState { |
||||||
|
user_items: User[]; |
||||||
|
user_status: Status; |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
import mysql from "mysql2"; |
||||||
|
|
||||||
|
const pool = mysql.createPool({ |
||||||
|
host: process.env.HOST, |
||||||
|
user: process.env.DATABASE, |
||||||
|
database: process.env.DATABASE, |
||||||
|
password: process.env.PASSWORD, |
||||||
|
waitForConnections: true, |
||||||
|
connectionLimit: 10, |
||||||
|
queueLimit: 0 |
||||||
|
}); |
||||||
|
|
||||||
|
module.exports = pool; |
@ -0,0 +1,15 @@ |
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ |
||||||
|
const pool = require("./connect"); |
||||||
|
|
||||||
|
interface definitionInterface{ |
||||||
|
(message:string):void; |
||||||
|
} |
||||||
|
export default function Insert(sql: string, argument: string[], callback: definitionInterface) { |
||||||
|
pool.query(sql, argument, (err, result) => { |
||||||
|
if (err) { |
||||||
|
return console.error(err.message); |
||||||
|
} |
||||||
|
callback(result.insertId); |
||||||
|
pool.releaseConnection(pool); |
||||||
|
}); |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ |
||||||
|
const pool = require("./connect"); |
||||||
|
import {QueryError} from 'mysql2'; |
||||||
|
|
||||||
|
interface definitionInterface{ |
||||||
|
(message:string):void; |
||||||
|
} |
||||||
|
export default function Select(sql: string, callback: definitionInterface) { |
||||||
|
pool.query(sql, (err: QueryError, rows: string) => { |
||||||
|
callback(rows); |
||||||
|
pool.releaseConnection(pool); |
||||||
|
}); |
||||||
|
} |
@ -0,0 +1,119 @@ |
|||||||
|
@tailwind base; |
||||||
|
@tailwind components; |
||||||
|
@tailwind utilities; |
||||||
|
|
||||||
|
@layer base { |
||||||
|
h1 { |
||||||
|
@apply mb-6 text-3xl font-semibold; |
||||||
|
} |
||||||
|
h2 { |
||||||
|
@apply text-2xl font-semibold; |
||||||
|
} |
||||||
|
p { |
||||||
|
@apply mb-4; |
||||||
|
} |
||||||
|
a { |
||||||
|
@apply text-blue-500 hover:text-blue-400 dark:text-blue-400 dark:hover:text-blue-300; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* Post styles */ |
||||||
|
.prose { |
||||||
|
max-width: 100vh; |
||||||
|
} |
||||||
|
|
||||||
|
.prose pre { |
||||||
|
@apply bg-gray-50 border border-gray-200 dark:border-gray-700 dark:bg-gray-900; |
||||||
|
} |
||||||
|
|
||||||
|
.prose code { |
||||||
|
@apply text-gray-800 dark:text-gray-200 px-1 py-0.5 border border-gray-100 dark:border-gray-800 rounded-md bg-gray-100 dark:bg-gray-900; |
||||||
|
} |
||||||
|
|
||||||
|
.prose img { |
||||||
|
/* Don't apply styles to next/image */ |
||||||
|
@apply m-0; |
||||||
|
} |
||||||
|
|
||||||
|
/* Prism Styles */ |
||||||
|
.token.comment, |
||||||
|
.token.prolog, |
||||||
|
.token.doctype, |
||||||
|
.token.cdata { |
||||||
|
@apply text-gray-700 dark:text-gray-300; |
||||||
|
} |
||||||
|
|
||||||
|
.token.punctuation { |
||||||
|
@apply text-gray-700 dark:text-gray-300; |
||||||
|
} |
||||||
|
|
||||||
|
.token.property, |
||||||
|
.token.tag, |
||||||
|
.token.boolean, |
||||||
|
.token.number, |
||||||
|
.token.constant, |
||||||
|
.token.symbol, |
||||||
|
.token.deleted { |
||||||
|
@apply text-green-500; |
||||||
|
} |
||||||
|
|
||||||
|
.token.selector, |
||||||
|
.token.attr-name, |
||||||
|
.token.string, |
||||||
|
.token.char, |
||||||
|
.token.builtin, |
||||||
|
.token.inserted { |
||||||
|
@apply text-purple-500; |
||||||
|
} |
||||||
|
|
||||||
|
.token.operator, |
||||||
|
.token.entity, |
||||||
|
.token.url, |
||||||
|
.language-css .token.string, |
||||||
|
.style .token.string { |
||||||
|
@apply text-yellow-500; |
||||||
|
} |
||||||
|
|
||||||
|
.token.atrule, |
||||||
|
.token.attr-value, |
||||||
|
.token.keyword { |
||||||
|
@apply text-blue-500; |
||||||
|
} |
||||||
|
|
||||||
|
.token.function, |
||||||
|
.token.class-name { |
||||||
|
@apply text-pink-500; |
||||||
|
} |
||||||
|
|
||||||
|
.token.regex, |
||||||
|
.token.important, |
||||||
|
.token.variable { |
||||||
|
@apply text-yellow-500; |
||||||
|
} |
||||||
|
|
||||||
|
code[class*='language-'], |
||||||
|
pre[class*='language-'] { |
||||||
|
@apply text-gray-800 dark:text-gray-50; |
||||||
|
} |
||||||
|
|
||||||
|
pre::-webkit-scrollbar { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
pre { |
||||||
|
-ms-overflow-style: none; /* IE and Edge */ |
||||||
|
scrollbar-width: none; /* Firefox */ |
||||||
|
} |
||||||
|
|
||||||
|
/* Remark Styles */ |
||||||
|
.remark-code-title { |
||||||
|
@apply text-gray-800 dark:text-gray-200 px-5 py-3 border border-b-0 border-gray-200 dark:border-gray-700 rounded-t bg-gray-200 dark:bg-gray-800 text-sm font-mono font-bold; |
||||||
|
} |
||||||
|
|
||||||
|
.remark-code-title + pre { |
||||||
|
@apply mt-0 rounded-t-none; |
||||||
|
} |
||||||
|
|
||||||
|
.mdx-marker { |
||||||
|
@apply block -mx-4 px-4 bg-gray-100 dark:bg-gray-800 border-l-4 border-blue-500; |
||||||
|
} |
@ -0,0 +1,75 @@ |
|||||||
|
const { spacing } = require('tailwindcss/defaultTheme'); |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
mode: 'jit', |
||||||
|
purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], |
||||||
|
darkMode: 'class', // or 'media' or 'class'
|
||||||
|
theme: { |
||||||
|
extend: { |
||||||
|
typography: (theme) => ({ |
||||||
|
DEFAULT: { |
||||||
|
css: { |
||||||
|
color: theme('colors.gray.700'), |
||||||
|
a: { |
||||||
|
color: theme('colors.blue.500'), |
||||||
|
'&:hover': { |
||||||
|
color: theme('colors.blue.700'), |
||||||
|
}, |
||||||
|
code: { color: theme('colors.blue.400') }, |
||||||
|
}, |
||||||
|
'h2,h3,h4': { |
||||||
|
'scroll-margin-top': spacing[32], |
||||||
|
}, |
||||||
|
code: { color: theme('colors.pink.500') }, |
||||||
|
'blockquote p:first-of-type::before': false, |
||||||
|
'blockquote p:last-of-type::after': false, |
||||||
|
}, |
||||||
|
}, |
||||||
|
dark: { |
||||||
|
css: { |
||||||
|
color: theme('colors.gray.300'), |
||||||
|
a: { |
||||||
|
color: theme('colors.blue.400'), |
||||||
|
'&:hover': { |
||||||
|
color: theme('colors.blue.600'), |
||||||
|
}, |
||||||
|
code: { color: theme('colors.blue.400') }, |
||||||
|
}, |
||||||
|
blockquote: { |
||||||
|
borderLeftColor: theme('colors.gray.800'), |
||||||
|
color: theme('colors.gray.300'), |
||||||
|
}, |
||||||
|
'h2,h3,h4': { |
||||||
|
color: theme('colors.gray.100'), |
||||||
|
'scroll-margin-top': spacing[32], |
||||||
|
}, |
||||||
|
hr: { borderColor: theme('colors.gray.800') }, |
||||||
|
ol: { |
||||||
|
li: { |
||||||
|
'&:before': { color: theme('colors.gray.500') }, |
||||||
|
}, |
||||||
|
}, |
||||||
|
ul: { |
||||||
|
li: { |
||||||
|
'&:before': { backgroundColor: theme('colors.gray.500') }, |
||||||
|
}, |
||||||
|
}, |
||||||
|
strong: { color: theme('colors.gray.300') }, |
||||||
|
thead: { |
||||||
|
color: theme('colors.gray.100'), |
||||||
|
}, |
||||||
|
tbody: { |
||||||
|
tr: { |
||||||
|
borderBottomColor: theme('colors.gray.700'), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}), |
||||||
|
}, |
||||||
|
}, |
||||||
|
variants: { |
||||||
|
typography: ['dark'], |
||||||
|
}, |
||||||
|
plugins: [require('@tailwindcss/typography')], |
||||||
|
}; |
@ -0,0 +1 @@ |
|||||||
|
module.exports = 'test-file-stub'; |
@ -0,0 +1,137 @@ |
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||||
|
|
||||||
|
exports[`Home page matches snapshot 1`] = ` |
||||||
|
<DocumentFragment> |
||||||
|
<div |
||||||
|
class="jsx-1276654382 container" |
||||||
|
> |
||||||
|
<main |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
<h1 |
||||||
|
class="jsx-1276654382 title" |
||||||
|
> |
||||||
|
Welcome to |
||||||
|
<a |
||||||
|
class="jsx-1276654382" |
||||||
|
href="https://nextjs.org" |
||||||
|
> |
||||||
|
Next.js! |
||||||
|
</a> |
||||||
|
</h1> |
||||||
|
<p |
||||||
|
class="jsx-1276654382 description" |
||||||
|
> |
||||||
|
Get started by editing |
||||||
|
<code |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
pages/index.tsx |
||||||
|
</code> |
||||||
|
</p> |
||||||
|
<button |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
Test Button |
||||||
|
</button> |
||||||
|
<div |
||||||
|
class="jsx-1276654382 grid" |
||||||
|
> |
||||||
|
<a |
||||||
|
class="jsx-1276654382 card" |
||||||
|
href="https://nextjs.org/docs" |
||||||
|
> |
||||||
|
<h3 |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
Documentation → |
||||||
|
</h3> |
||||||
|
<p |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
Find in-depth information about Next.js features and API. |
||||||
|
</p> |
||||||
|
</a> |
||||||
|
<a |
||||||
|
class="jsx-1276654382 card" |
||||||
|
href="https://nextjs.org/learn" |
||||||
|
> |
||||||
|
<h3 |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
Learn → |
||||||
|
</h3> |
||||||
|
<p |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
Learn about Next.js in an interactive course with quizzes! |
||||||
|
</p> |
||||||
|
</a> |
||||||
|
<a |
||||||
|
class="jsx-1276654382 card" |
||||||
|
href="https://github.com/vercel/next.js/tree/master/examples" |
||||||
|
> |
||||||
|
<h3 |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
Examples → |
||||||
|
</h3> |
||||||
|
<p |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
Discover and deploy boilerplate example Next.js projects. |
||||||
|
</p> |
||||||
|
</a> |
||||||
|
<a |
||||||
|
class="jsx-1276654382 card" |
||||||
|
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app" |
||||||
|
> |
||||||
|
<h3 |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
Deploy → |
||||||
|
</h3> |
||||||
|
<p |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
Instantly deploy your Next.js site to a public URL with Vercel. |
||||||
|
</p> |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
</main> |
||||||
|
<footer |
||||||
|
class="jsx-1276654382" |
||||||
|
> |
||||||
|
<a |
||||||
|
class="jsx-1276654382" |
||||||
|
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app" |
||||||
|
rel="noopener noreferrer" |
||||||
|
target="_blank" |
||||||
|
> |
||||||
|
Powered by |
||||||
|
<div |
||||||
|
style="display: inline-block; max-width: 100%; overflow: hidden; position: relative; box-sizing: border-box; margin: 0px;" |
||||||
|
> |
||||||
|
<div |
||||||
|
style="box-sizing: border-box; display: block; max-width: 100%;" |
||||||
|
> |
||||||
|
<img |
||||||
|
alt="" |
||||||
|
aria-hidden="true" |
||||||
|
role="presentation" |
||||||
|
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4xIi8+" |
||||||
|
style="max-width: 100%; display: block; margin: 0px; padding: 0px;" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<img |
||||||
|
alt="Vercel Logo" |
||||||
|
decoding="async" |
||||||
|
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" |
||||||
|
style="visibility: hidden; position: absolute; top: 0px; left: 0px; bottom: 0px; right: 0px; box-sizing: border-box; padding: 0px; margin: auto; display: block; width: 0px; height: 0px; min-width: 100%; max-width: 100%; min-height: 100%; max-height: 100%;" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</a> |
||||||
|
</footer> |
||||||
|
</div> |
||||||
|
</DocumentFragment> |
||||||
|
`; |
@ -0,0 +1,24 @@ |
|||||||
|
import { render } from '@testing-library/react'; |
||||||
|
// import { ThemeProvider } from "my-ui-lib"
|
||||||
|
// import { TranslationProvider } from "my-i18n-lib"
|
||||||
|
// import defaultStrings from "i18n/en-x-default"
|
||||||
|
|
||||||
|
const Providers = ({ children }) => { |
||||||
|
return children; |
||||||
|
// return (
|
||||||
|
// <ThemeProvider theme="light">
|
||||||
|
// <TranslationProvider messages={defaultStrings}>
|
||||||
|
// {children}
|
||||||
|
// </TranslationProvider>
|
||||||
|
// </ThemeProvider>
|
||||||
|
// )
|
||||||
|
}; |
||||||
|
|
||||||
|
const customRender = (ui, options = {}) => |
||||||
|
render(ui, { wrapper: Providers, ...options }); |
||||||
|
|
||||||
|
// re-export everything
|
||||||
|
export * from '@testing-library/react'; |
||||||
|
|
||||||
|
// override render method
|
||||||
|
export { customRender as render }; |
@ -0,0 +1,33 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "es5", |
||||||
|
"lib": [ |
||||||
|
"dom", |
||||||
|
"dom.iterable", |
||||||
|
"esnext" |
||||||
|
], |
||||||
|
"allowJs": true, |
||||||
|
"skipLibCheck": true, |
||||||
|
"strict": false, |
||||||
|
"forceConsistentCasingInFileNames": true, |
||||||
|
"noEmit": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"module": "esnext", |
||||||
|
"moduleResolution": "node", |
||||||
|
"resolveJsonModule": true, |
||||||
|
"isolatedModules": true, |
||||||
|
"jsx": "preserve", |
||||||
|
"incremental": true |
||||||
|
}, |
||||||
|
"exclude": [ |
||||||
|
"node_modules", |
||||||
|
".next", |
||||||
|
"out" |
||||||
|
], |
||||||
|
"include": [ |
||||||
|
"next-env.d.ts", |
||||||
|
"**/*.ts", |
||||||
|
"**/*.tsx", |
||||||
|
"**/*.js" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
import { PostType } from './post'; |
||||||
|
|
||||||
|
export interface MetaProps |
||||||
|
extends Pick<PostType, 'date' | 'description' | 'image' | 'title'> { |
||||||
|
/** |
||||||
|
* For the meta tag `og:type` |
||||||
|
*/ |
||||||
|
type?: string; |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
export type PostType = { |
||||||
|
date?: string; |
||||||
|
description?: string; |
||||||
|
image?: string; |
||||||
|
slug: string; |
||||||
|
title: string; |
||||||
|
}; |
@ -0,0 +1,11 @@ |
|||||||
|
import fs from 'fs'; |
||||||
|
import path from 'path'; |
||||||
|
|
||||||
|
// POSTS_PATH is useful when you want to get the path to a specific file
|
||||||
|
export const POSTS_PATH = path.join(process.cwd(), 'posts'); |
||||||
|
|
||||||
|
// postFilePaths is the list of all mdx files inside the POSTS_PATH directory
|
||||||
|
export const postFilePaths = fs |
||||||
|
.readdirSync(POSTS_PATH) |
||||||
|
// Only include md(x) files
|
||||||
|
.filter((path) => /\.mdx?$/.test(path)); |
Loading…
Reference in new issue