diff --git a/package.json b/package.json index ad4d8e8..1e0a5e3 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "axios": "^1.4.0", + "chart.js": "^4.4.0", "clsx": "^2.0.0", "date-fns": "^2.30.0", "focus-visible": "^5.2.0", diff --git a/src/api/deck/getDeck.ts b/src/api/deck/getDeck.ts new file mode 100644 index 0000000..20a1b84 --- /dev/null +++ b/src/api/deck/getDeck.ts @@ -0,0 +1,49 @@ +import {Question} from './types'; +import {axios} from '../../lib/axios'; +import { + DECK_API_URL, + QUESTION_API_URL, + QUESTION_PARAM_DECK_ID, + QUESTION_PARAM_QUESTION_ID, +} from './urlKeys'; +import {ExtractFnReturnType, QueryConfig} from '../../lib/react-query'; +import {useQuery} from '@tanstack/react-query'; +import {QUERY_KEY_DECKS, QUERY_KEY_QUESTION} from './queryKeys'; + +export type GetDeckResponse = { + deck: { + name: string; + description: string; + }, + slides: { + slide: number; + data: { + slug: string; + answer: any; + }[]; + }[]; +}; + +export const getDeck = ({ deckId }: { deckId: number }): Promise => { + return axios.get( + DECK_API_URL + .replace(`:${QUESTION_PARAM_DECK_ID}`, String(deckId)) + ); +}; + +type QueryFnType = typeof getDeck; + +type UseDeckOptions = { + deckId: number; + config?: QueryConfig; +}; + +export const useDeck = ({ deckId, config }: UseDeckOptions) => { + return useQuery>({ + ...config, + queryKey: [QUERY_KEY_DECKS, deckId], + queryFn: async () => { + return await getDeck({ deckId }); + }, + }); +}; diff --git a/src/api/deck/urlKeys.ts b/src/api/deck/urlKeys.ts index 53c8b2b..e008f4a 100644 --- a/src/api/deck/urlKeys.ts +++ b/src/api/deck/urlKeys.ts @@ -1,9 +1,11 @@ export const DECKS_API_URL = '/decks/'; - export const FIRST_QUESTION_PARAM = 'deckId'; export const FIRST_QUESTION_API_URL = `/decks/question/:${FIRST_QUESTION_PARAM}`; export const QUESTION_PARAM_DECK_ID = 'deckId'; export const QUESTION_PARAM_QUESTION_ID = 'questionId'; -export const QUESTION_API_URL = `/decks/question/:${FIRST_QUESTION_PARAM}/:${QUESTION_PARAM_QUESTION_ID}`; \ No newline at end of file +export const QUESTION_API_URL = `/decks/question/:${FIRST_QUESTION_PARAM}/:${QUESTION_PARAM_QUESTION_ID}`; + +export const DECK_PARAM = 'deckId'; +export const DECK_API_URL = `/decks/question/:${FIRST_QUESTION_PARAM}/presentation`; \ No newline at end of file diff --git a/src/app/styles/baseText.scss b/src/app/styles/baseText.scss index d776f57..8f210f3 100644 --- a/src/app/styles/baseText.scss +++ b/src/app/styles/baseText.scss @@ -1,6 +1,5 @@ $font-family-raleway: 'Raleway'; $font-family-ibm-plex-mono: 'IBM Plex Mono'; -$font-family-lora: 'Lora', serif; $font-family-default: system-ui, /* macOS 10.11-10.12 */ -apple-system, /* Windows 6+ */ 'Segoe UI', /* Android 4+ */ 'Roboto', /* Ubuntu 10.10+ */ 'Ubuntu', /* Gnome 3+ */ 'Cantarell', /* KDE Plasma 5+ */ 'Noto Sans', @@ -152,10 +151,3 @@ $line-height-67: 67px; font-weight: $font-weight-400; line-height: $line-height-16; } - -@mixin text-presentation { - font-family: $font-family-lora; - font-size: $font-size-16; - font-weight: $font-weight-400; - line-height: $line-height-20; -} diff --git a/src/app/styles/fonts.scss b/src/app/styles/fonts.scss index 0a371db..e91c668 100644 --- a/src/app/styles/fonts.scss +++ b/src/app/styles/fonts.scss @@ -45,12 +45,4 @@ $fonts-path: '../../assets/fonts' !default; font-weight: $font-weight-400; font-style: normal; font-display: swap; -} - -@font-face { - font-family: $font-family-lora; - src: url('#{$fonts-path}/Lora/Lora-Regular.ttf') format('truetype'); - font-weight: $font-weight-400; - font-style: normal; - font-display: swap; } \ No newline at end of file diff --git a/src/assets/fonts/Lora/Lora-Bold.ttf b/src/assets/fonts/Lora/Lora-Bold.ttf deleted file mode 100755 index cd154bf..0000000 Binary files a/src/assets/fonts/Lora/Lora-Bold.ttf and /dev/null differ diff --git a/src/assets/fonts/Lora/Lora-BoldItalic.ttf b/src/assets/fonts/Lora/Lora-BoldItalic.ttf deleted file mode 100755 index 9d61f1b..0000000 Binary files a/src/assets/fonts/Lora/Lora-BoldItalic.ttf and /dev/null differ diff --git a/src/assets/fonts/Lora/Lora-Italic.ttf b/src/assets/fonts/Lora/Lora-Italic.ttf deleted file mode 100755 index c037576..0000000 Binary files a/src/assets/fonts/Lora/Lora-Italic.ttf and /dev/null differ diff --git a/src/assets/fonts/Lora/Lora-Medium.ttf b/src/assets/fonts/Lora/Lora-Medium.ttf deleted file mode 100755 index 6d652b5..0000000 Binary files a/src/assets/fonts/Lora/Lora-Medium.ttf and /dev/null differ diff --git a/src/assets/fonts/Lora/Lora-MediumItalic.ttf b/src/assets/fonts/Lora/Lora-MediumItalic.ttf deleted file mode 100755 index f52f150..0000000 Binary files a/src/assets/fonts/Lora/Lora-MediumItalic.ttf and /dev/null differ diff --git a/src/assets/fonts/Lora/Lora-Regular.ttf b/src/assets/fonts/Lora/Lora-Regular.ttf deleted file mode 100755 index 5306a94..0000000 Binary files a/src/assets/fonts/Lora/Lora-Regular.ttf and /dev/null differ diff --git a/src/assets/fonts/Lora/Lora-SemiBold.ttf b/src/assets/fonts/Lora/Lora-SemiBold.ttf deleted file mode 100755 index c69d778..0000000 Binary files a/src/assets/fonts/Lora/Lora-SemiBold.ttf and /dev/null differ diff --git a/src/assets/fonts/Lora/Lora-SemiBoldItalic.ttf b/src/assets/fonts/Lora/Lora-SemiBoldItalic.ttf deleted file mode 100755 index c3d61ee..0000000 Binary files a/src/assets/fonts/Lora/Lora-SemiBoldItalic.ttf and /dev/null differ diff --git a/src/assets/icons/loader.svg b/src/assets/icons/loader.svg new file mode 100644 index 0000000..80eb39d --- /dev/null +++ b/src/assets/icons/loader.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Hint/Hint.module.scss b/src/components/Hint/Hint.module.scss index 5157a90..f7d347c 100644 --- a/src/components/Hint/Hint.module.scss +++ b/src/components/Hint/Hint.module.scss @@ -54,7 +54,7 @@ position: absolute; top: -4px; right: -4px; - background-color: white; + background-color: $color-on-surface-light-100; z-index: 1; padding: 2px 4px; width: 16px; diff --git a/src/components/Loader/Loader.module.scss b/src/components/Loader/Loader.module.scss new file mode 100644 index 0000000..cd51469 --- /dev/null +++ b/src/components/Loader/Loader.module.scss @@ -0,0 +1,25 @@ +@import 'src/app/styles/vars'; + +.Loader { + display: flex; + align-items: center; + width: 20px; + height: 20px; +} + +.LoaderIcon { + width: 100%; + height: 100%; + color: $color-on-surface-dark-100; + animation: spinner 0.7s linear infinite; +} + +@keyframes spinner { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx new file mode 100644 index 0000000..8b599f9 --- /dev/null +++ b/src/components/Loader/Loader.tsx @@ -0,0 +1,35 @@ +import { memo } from 'react'; +import clsx from 'clsx'; +import s from './Loader.module.scss'; +import { ReactComponent as LoaderIcon } from '../../assets/icons/loader.svg'; + +// export enum LoaderSize { +// /** +// * Размер лоадера 20х20 +// */ +// small = 'small', +// /** +// * Размер лоадера 30х30 +// */ +// medium = 'medium', +// /** +// * Размер лоадера 40х40 +// */ +// large = 'large' +// } + +export interface LoaderProps { + className?: string; +} + +export const Loader = memo((props: LoaderProps) => { + const { className } = props; + + return ( +
+ +
+ ); +}); + +Loader.displayName = 'Loader'; diff --git a/src/components/Loader/index.ts b/src/components/Loader/index.ts new file mode 100644 index 0000000..f51e655 --- /dev/null +++ b/src/components/Loader/index.ts @@ -0,0 +1 @@ +export * from './Loader'; \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 261c59c..752b757 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,3 +1,3 @@ -// export const API_URL = 'http://192.168.83.181:8000/api'; +export const API_URL = 'http://192.168.83.181:8000/api'; // export const API_URL = 'http://192.168.22.4:8000/api'; -export const API_URL = 'https://ed68-77-234-219-9.ngrok-free.app/api'; \ No newline at end of file +// export const API_URL = 'https://ed68-77-234-219-9.ngrok-free.app/api'; \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 9f1088a..1bad816 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,38 +1,28 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import reportWebVitals from './reportWebVitals'; -// import {App} from './app'; +import {App} from './app'; import {AppProvider} from './app'; -import {PDFViewer, pdf} from '@react-pdf/renderer'; -import {Document} from './pages/deck/document/Document'; -// import 'focus-visible'; -// -// import 'sanitize.css'; -// import 'sanitize.css/forms.css'; -// import 'sanitize.css/typography.css'; -// import './app/styles/index.scss'; +import 'focus-visible'; + +import 'sanitize.css'; +import 'sanitize.css/forms.css'; +import 'sanitize.css/typography.css'; +import './app/styles/index.scss'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); -// root.render( -// // -// -// -// -// // -// ); +root.render( + // + + + + // +); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -// reportWebVitals(); - -const App = () => ( - - - -); - -root.render(); \ No newline at end of file +reportWebVitals(); diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx index 576f015..e993349 100644 --- a/src/pages/chat/ChatPage.tsx +++ b/src/pages/chat/ChatPage.tsx @@ -16,6 +16,8 @@ import {usePrevious} from '../../hooks/usePrevious'; import {generateAnswerFromData} from './utils/generateAnswerFromData'; import {generateTextFromAnswer} from './utils/generateTextFromAnswer'; import {Button, ButtonVariant} from '../../components/Button'; +import {useNavigate} from 'react-router-dom'; +import {DECK_PAGE_PARAM, DECK_PAGE_ROUTE} from '../../app/routes'; export interface ChatPageProps { /** @@ -26,8 +28,8 @@ export interface ChatPageProps { const QUESTION_POLLING_MS = 1000; -const DEFAULT_DECK_ID = 65; -const DEFAULT_QUESTION_ID = 32; +const DEFAULT_DECK_ID = 0; +const DEFAULT_QUESTION_ID = 0; export const ChatPage: ReactFCC = (props) => { const {className} = props; @@ -115,6 +117,7 @@ export const ChatPage: ReactFCC = (props) => { const { mutateAsync: createAnswer } = useCreateAnswer(); + const navigate = useNavigate(); const onSubmit: SubmitHandler = useCallback(async (data) => { if (!question || !data.value) { @@ -136,16 +139,24 @@ export const ChatPage: ReactFCC = (props) => { text: generateTextFromAnswer(question.type, answer) }); - setQuestionId(question!.next_id); + if (question.next_id) { + setQuestionId(question!.next_id); + } else { + navigate(DECK_PAGE_ROUTE.replace(`:${DECK_PAGE_PARAM}`, String(deckId))) + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [createAnswer, deckId, pushHistory, question, questionId]); // Пропуск вопроса const onSkip = useCallback(() => { if (question && !question.params?.required) { - setQuestionId(question.next_id); + if (question.next_id) { + setQuestionId(question!.next_id); + } else { + navigate(DECK_PAGE_ROUTE.replace(`:${DECK_PAGE_PARAM}`, String(deckId))) + } } - }, [question]); + }, [deckId, navigate, question]); // ---------- Скролл чата ---------- // todo при печатании текста тоже двигать скролл diff --git a/src/pages/chat/components/ChatForm/components/ChatFormMultipleDateDescription/ChatFormMultipleDateDescription.tsx b/src/pages/chat/components/ChatForm/components/ChatFormMultipleDateDescription/ChatFormMultipleDateDescription.tsx index fa0cd0d..3c1e468 100644 --- a/src/pages/chat/components/ChatForm/components/ChatFormMultipleDateDescription/ChatFormMultipleDateDescription.tsx +++ b/src/pages/chat/components/ChatForm/components/ChatFormMultipleDateDescription/ChatFormMultipleDateDescription.tsx @@ -36,7 +36,6 @@ export const ChatFormMultipleDateDescription: ReactFCC diff --git a/src/pages/chat/components/ChatForm/components/ChatFormMultipleLinkDescription/ChatFormMultipleLinkDescription.tsx b/src/pages/chat/components/ChatForm/components/ChatFormMultipleLinkDescription/ChatFormMultipleLinkDescription.tsx index 485c847..99234c2 100644 --- a/src/pages/chat/components/ChatForm/components/ChatFormMultipleLinkDescription/ChatFormMultipleLinkDescription.tsx +++ b/src/pages/chat/components/ChatForm/components/ChatFormMultipleLinkDescription/ChatFormMultipleLinkDescription.tsx @@ -49,7 +49,6 @@ export const ChatFormMultipleLinkDescription: ReactFCC diff --git a/src/pages/deck/DeckPage.module.scss b/src/pages/deck/DeckPage.module.scss new file mode 100644 index 0000000..cb42af6 --- /dev/null +++ b/src/pages/deck/DeckPage.module.scss @@ -0,0 +1,14 @@ +@import 'src/app/styles/vars'; + +.DeckPage { + position: relative; + height: 100svh; +} + +.DeckPage__loader { + width: 50px !important; + height: 50px !important; + position: absolute; + top: calc(50% - 25px); + left: calc(50% - 25px); +} \ No newline at end of file diff --git a/src/pages/deck/DeckPage.tsx b/src/pages/deck/DeckPage.tsx index ac9c693..608054f 100644 --- a/src/pages/deck/DeckPage.tsx +++ b/src/pages/deck/DeckPage.tsx @@ -2,15 +2,89 @@ import {ReactFCC} from '../../utils/ReactFCC'; import {PDFViewer} from '@react-pdf/renderer'; import {useUrlParam} from '../../hooks/useUrlParam'; import {DECK_PAGE_PARAM} from '../../app/routes'; -import {Document} from './document/Document'; +import {MyDocument} from './document/Document'; +import {useDeck} from '../../api/deck/getDeck'; +import {useEffect, useRef, useState} from 'react'; +import {generateMarketChart} from './document/media/generateMarketChart'; +import {Loader} from '../../components/Loader'; +import s from './DeckPage.module.scss'; +import {generateFinancialChart} from './document/media/generateFinancialChart'; +import {generateGrowChart} from './document/media/generateGrowChart'; export const DeckPage: ReactFCC = () => { const deckId = useUrlParam(DECK_PAGE_PARAM, {parser: parseInt}); - console.log(deckId); + + const { data } = useDeck({ + deckId: deckId ?? 0, + config: { + enabled: !!deckId + } + }); + + const [marketChart, setMarketChart] = useState(''); + const [financialChart, setFinancialChart] = useState(''); + const [growChart, setGrowChart] = useState(''); + + const [rendered, setRendered] = useState(false); + + const initRef = useRef(false); + + useEffect(() => { + if (!data || initRef.current) { + return; + } + + initRef.current = true; + + const market_values = data.slides[4].data.find((i) => i.slug === 'market_values')?.answer as any; + const users_metrics = data.slides[7].data.find((i) => i.slug === 'users_metrics')?.answer as any; + + const company_value = data.slides[10].data.find((i) => i.slug === 'company_value')?.answer.sum as number; + const future_value = data.slides[10].data.find((i) => i.slug === 'future_value')?.answer.sum as number; + const time_to_spend = data.slides[10].data.find((i) => i.slug === 'time_to_spend')?.answer as any; + + generateMarketChart( + 'chart1', + [market_values.tam, market_values.sam, market_values.som], + (data) => setMarketChart(data) + ); + + generateFinancialChart( + 'chart2', + [users_metrics.cac, users_metrics.ltv], + (data) => setFinancialChart(data) + ); + + generateGrowChart( + 'chart3', + [company_value, future_value, time_to_spend], + (data) => setGrowChart(data) + ); + }, [data]); + return ( - - - +
+ {!rendered && ( + + )} + {data && marketChart && financialChart && growChart ? ( + + setRendered(true)} + data={data} + marketChart={marketChart} + financialChart={financialChart} + growChart={growChart} + /> + + ) : ( +
+ + + +
+ )} +
) } \ No newline at end of file diff --git a/src/pages/deck/document/Document.tsx b/src/pages/deck/document/Document.tsx index 8aca4c2..745f8f3 100644 --- a/src/pages/deck/document/Document.tsx +++ b/src/pages/deck/document/Document.tsx @@ -1,33 +1,54 @@ import {ReactFCC} from '../../../utils/ReactFCC'; -import {Page, View, Text, StyleSheet} from '@react-pdf/renderer'; +import {Document, Font} from '@react-pdf/renderer'; +import {Slide1} from './slides/Slide1'; +import {GetDeckResponse} from '../../../api/deck/getDeck'; +import {Slide2} from './slides/Slide2'; +import {Slide3} from './slides/Slide3'; +import {Slide4} from './slides/Slide4'; +import {Slide5} from './slides/Slide5'; +import {Slide6} from './slides/Slide6'; +import {Slide8} from './slides/Slide8'; +import {Slide7} from './slides/Slide7'; +import Chart from 'chart.js/auto'; +import {Slide9} from './slides/Slide9'; +import {Slide10} from './slides/Slide10'; +import {Slide11} from './slides/Slide11'; +import {Slide12} from './slides/Slide12'; +import {Slide13} from './slides/Slide13'; -export interface Props { +Chart.defaults.font.size = 20; +export interface MyDocumentProps { + data: GetDeckResponse; + onRender: () => void; + marketChart: string; + financialChart: string; + growChart: string; } -const styles = StyleSheet.create({ - page: { - flexDirection: 'row', - backgroundColor: '#E4E4E4' - }, - section: { - margin: 10, - padding: 10, - flexGrow: 1 - } +Font.register({ + family: "Roboto", + src: "https://cdnjs.cloudflare.com/ajax/libs/ink/3.1.10/fonts/Roboto/roboto-medium-webfont.ttf" }); -export const Document: ReactFCC = (props) => { +export const MyDocument: ReactFCC = (props) => { + const { data, marketChart, financialChart, growChart, onRender } = props; + return ( - - - - Section #1 - - - Section #2 - - + + + + + + + + + + + + + + ); }; diff --git a/src/pages/deck/document/media/generateFinancialChart.ts b/src/pages/deck/document/media/generateFinancialChart.ts new file mode 100644 index 0000000..5d7f246 --- /dev/null +++ b/src/pages/deck/document/media/generateFinancialChart.ts @@ -0,0 +1,28 @@ +import Chart from 'chart.js/auto'; + +export const generateFinancialChart = (rootId: string, [cac, ltv]: [number, number], callback: (data: string) => void) => { + const chart = new Chart((document.getElementById(rootId) as HTMLCanvasElement).getContext('2d') as CanvasRenderingContext2D, { + type: 'bar', + data: { + labels: ['LTV', 'CAC'], + datasets: [ + { + label: 'Сравнение LTV (Lifetime Value) и CAC (Customer Acquisition Cost)', + data: [ltv, cac], + backgroundColor: 'rgba(255, 99, 132, 0.3)', + borderColor: 'rgba(255,99,132,1)', + borderWidth: 1, + }, + ], + }, + options: { + indexAxis: 'y', + aspectRatio: 2, + animation: { + onComplete: function () { + callback(chart.toBase64Image()); + }, + }, + }, + }) +} \ No newline at end of file diff --git a/src/pages/deck/document/media/generateGrowChart.ts b/src/pages/deck/document/media/generateGrowChart.ts new file mode 100644 index 0000000..eed1f5d --- /dev/null +++ b/src/pages/deck/document/media/generateGrowChart.ts @@ -0,0 +1,32 @@ +import Chart from 'chart.js/auto'; +import format from 'date-fns/format'; +import addYears from 'date-fns/addYears'; + +export const generateGrowChart = (rootId: string, [company_value, future_value, date]: [number, number, Date], callback: (data: string) => void) => { + const chart = new Chart((document.getElementById(rootId) as HTMLCanvasElement).getContext('2d') as CanvasRenderingContext2D, { + type: 'line', + data: { + labels: [ + format(new Date(), 'dd.MM.yyyy'), + format(date ? new Date(date) : addYears(new Date(), 1), 'dd.MM.yyyy')], + datasets: [ + { + label: 'Рост после привлечения инвестиций', + data: [company_value, future_value], + fill: false, + borderColor: 'rgba(255, 99, 132, 0.5)', + tension: 0.1 + }, + ], + }, + options: { + // indexAxis: 'y', + aspectRatio: 2, + animation: { + onComplete: function () { + callback(chart.toBase64Image()); + }, + }, + }, + }) +} \ No newline at end of file diff --git a/src/pages/deck/document/media/generateMarketChart.ts b/src/pages/deck/document/media/generateMarketChart.ts new file mode 100644 index 0000000..7c643c7 --- /dev/null +++ b/src/pages/deck/document/media/generateMarketChart.ts @@ -0,0 +1,28 @@ +import Chart from 'chart.js/auto'; + +export const generateMarketChart = (rootId: string, [tam, sam, som]: [number, number, number], callback: (data: string) => void) => { + const chart = new Chart((document.getElementById(rootId) as HTMLCanvasElement).getContext('2d') as CanvasRenderingContext2D, { + type: 'bar', + data: { + labels: ['SAM', 'SOM'], + datasets: [ + { + label: 'Сравнение SAM и SOM', + data: [sam, som], + backgroundColor: 'rgba(255, 99, 132, 0.3)', + borderColor: 'rgba(255,99,132,1)', + borderWidth: 1, + }, + ], + }, + options: { + indexAxis: 'y', + aspectRatio: 2, + animation: { + onComplete: function () { + callback(chart.toBase64Image()); + }, + }, + }, + }) +} \ No newline at end of file diff --git a/src/pages/deck/document/shared.ts b/src/pages/deck/document/shared.ts new file mode 100644 index 0000000..d1628ac --- /dev/null +++ b/src/pages/deck/document/shared.ts @@ -0,0 +1,2 @@ +export const bgColor = '#EFEEE7'; +export const primaryColor = '#2B2C30'; \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide1.tsx b/src/pages/deck/document/slides/Slide1.tsx new file mode 100644 index 0000000..c639a15 --- /dev/null +++ b/src/pages/deck/document/slides/Slide1.tsx @@ -0,0 +1,81 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import format from 'date-fns/format'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; + +export interface Slide1Props { + title: string; + description: string; + data: ExtractArray['data']; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + padding: '56px', + fontFamily: 'Roboto' + }, + mainBlock: { + flex: '1', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'stretch' + }, + title: { + fontSize: 96, + width: '100%', + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '0 0' + }, + description: { + marginTop: '16px', + width: '100%', + fontSize: '20px', + }, + secondaryBlock: { + // + }, + info: { + display: 'flex', + flexDirection: 'column', + rowGap: 4 + }, + infoText: { + fontFamily: 'Helvetica', + fontSize: '14px', + } +}); + +export const Slide1: ReactFCC = (props) => { + const { title, description, data } = props; + + const name = data.find((i) => i.slug === 'names')?.answer as string; + + return ( + + + {name} + + {description} + + + + + Henrietta Mitchell, Founder & CEO + {format(new Date(), 'dd MMM, yyyy')} + + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide10.tsx b/src/pages/deck/document/slides/Slide10.tsx new file mode 100644 index 0000000..1a4ae73 --- /dev/null +++ b/src/pages/deck/document/slides/Slide10.tsx @@ -0,0 +1,69 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; +import {currencyFormatter, formatDate} from '../../../../utils/fomat'; + +export interface Slide10Props { + data: ExtractArray['data']; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + marginBottom: 24 + }, + subtitle: { + width: '100%', + fontSize: '24px', + }, +}); + +export const Slide10: ReactFCC = (props) => { + const { data } = props; + + const how_much_investments = data.find((i) => i.slug === 'how_much_investments')?.answer.sum as any; + const time_to_spend = data.find((i) => i.slug === 'time_to_spend')?.answer as any; + const investments_sold = data.find((i) => i.slug === 'investments_sold')?.answer as any; + + return ( + + + Инвестиционный раунд + + + Сколько инвестиций необходимо + {currencyFormatter.format(how_much_investments)} + + Цели инвестиций + {investments_sold} + + Срок освоения инвестиций + {formatDate(time_to_spend)} + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide11.tsx b/src/pages/deck/document/slides/Slide11.tsx new file mode 100644 index 0000000..26cf679 --- /dev/null +++ b/src/pages/deck/document/slides/Slide11.tsx @@ -0,0 +1,63 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; + +export interface Slide11Props { + data: ExtractArray['data']; + growChart: string; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + marginBottom: 24 + }, + subtitle: { + width: '100%', + fontSize: '24px', + }, + image: { + maxWidth: '600px', + maxHeight: '300px', + margin: '50 auto 0' + } +}); + +export const Slide11: ReactFCC = (props) => { + const { data, growChart } = props; + + return ( + + + Инвестиционный раунд + + + + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide12.tsx b/src/pages/deck/document/slides/Slide12.tsx new file mode 100644 index 0000000..b622c9f --- /dev/null +++ b/src/pages/deck/document/slides/Slide12.tsx @@ -0,0 +1,91 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; +import {formatDate} from '../../../../utils/fomat'; +import {Fragment} from 'react'; + +export interface Slide12Props { + data: ExtractArray['data']; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + subtitle: { + fontSize: '18px', + marginBottom: '4px', + marginLeft: '-6px' + }, + text: { + fontSize: '14px', + marginLeft: '-6px' + }, + map: { + display: 'flex', + flexDirection: 'row', + marginTop: 70 + }, + item: { + flex: 1, + display: 'flex', + flexDirection: 'column', + borderTop: '1px solid black', + padding: '16px 16px 0 0', + }, + dot: { + width: 8, + height: 8, + backgroundColor: 'black', + borderRadius: '50%', + marginTop: '-3.5px' + } +}); + +export const Slide12: ReactFCC = (props) => { + const { data } = props; + + const aims = data.find((i) => i.slug === 'aims')?.answer as any; + + return ( + + + Роадмап + + + + + {Object.entries(aims).map(([date, text], index) => ( + + + {formatDate(date)} + {text as string} + + + + ))} + + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide13.tsx b/src/pages/deck/document/slides/Slide13.tsx new file mode 100644 index 0000000..a5781e5 --- /dev/null +++ b/src/pages/deck/document/slides/Slide13.tsx @@ -0,0 +1,67 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; +import {formatDate} from '../../../../utils/fomat'; +import {Fragment} from 'react'; + +export interface Slide13Props { + data: ExtractArray['data']; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + marginBottom: 24 + }, + subtitle: { + width: '100%', + fontSize: '24px', + marginBottom: 24 + }, +}); + +export const Slide13: ReactFCC = (props) => { + const { data } = props; + + const links = data.find((i) => i.slug === 'links')?.answer as any; + + return ( + + + Контакты + + + {Object.entries(links).map(([name, link], index) => ( + + {`${name}: ${link}`} + {/*{text as string}*/} + + ))} + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide2.tsx b/src/pages/deck/document/slides/Slide2.tsx new file mode 100644 index 0000000..19cae93 --- /dev/null +++ b/src/pages/deck/document/slides/Slide2.tsx @@ -0,0 +1,53 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; + +export interface Slide2Props { + data: ExtractArray['data']; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + }, +}); + +export const Slide2: ReactFCC = (props) => { + const { data } = props; + + const problem = data.find((i) => i.slug === 'problems')?.answer as string; + + return ( + + + Проблема + + {problem} + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide3.tsx b/src/pages/deck/document/slides/Slide3.tsx new file mode 100644 index 0000000..5016693 --- /dev/null +++ b/src/pages/deck/document/slides/Slide3.tsx @@ -0,0 +1,53 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; + +export interface SlideProps { + data: ExtractArray['data']; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + }, +}); + +export const Slide3: ReactFCC = (props) => { + const { data } = props; + + const actuality = data.find((i) => i.slug === 'actuality')?.answer as string; + + return ( + + + Описание и ценностное предложение стартапа + + {actuality} + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide4.tsx b/src/pages/deck/document/slides/Slide4.tsx new file mode 100644 index 0000000..1ca2f22 --- /dev/null +++ b/src/pages/deck/document/slides/Slide4.tsx @@ -0,0 +1,56 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; + +export interface SlideProps { + data: ExtractArray['data']; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + marginBottom: 24 + }, +}); + +export const Slide4: ReactFCC = (props) => { + const { data } = props; + + const solve = data.find((i) => i.slug === 'solve')?.answer as string; + const works = data.find((i) => i.slug === 'works')?.answer as string; + + return ( + + + Решение + + {solve} + {works} + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide5.tsx b/src/pages/deck/document/slides/Slide5.tsx new file mode 100644 index 0000000..7a07405 --- /dev/null +++ b/src/pages/deck/document/slides/Slide5.tsx @@ -0,0 +1,62 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; + +export interface Slide5Props { + data: ExtractArray['data']; + marketChart: string; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + marginBottom: 24 + }, + image: { + maxWidth: '600px', + maxHeight: '300px', + margin: '0 auto' + } +}); + +export const Slide5: ReactFCC = (props) => { + const { data, marketChart } = props; + + const market_values = data.find((i) => i.slug === 'market_values')?.answer as any; + const users = data.find((i) => i.slug === 'users')?.answer as string; + + return ( + + + Рынок + + Целевая аудитория: {users} + + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide6.tsx b/src/pages/deck/document/slides/Slide6.tsx new file mode 100644 index 0000000..6232add --- /dev/null +++ b/src/pages/deck/document/slides/Slide6.tsx @@ -0,0 +1,71 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; + +export interface Slide6Props { + data: ExtractArray['data']; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + marginBottom: 24 + }, + subtitle: { + width: '100%', + fontSize: '24px', + }, +}); + +export const Slide6: ReactFCC = (props) => { + const { data } = props; + + const competitors = data.find((i) => i.slug === 'competitors')?.answer as string; + const competitors_strength = data.find((i) => i.slug === 'competitors_strength')?.answer as string; + const competitors_low = data.find((i) => i.slug === 'competitors_low')?.answer as string; + const advantages = data.find((i) => i.slug === 'advantages')?.answer as string; + + return ( + + + Конкуренты + + + {competitors} + + Сильные стороны + {competitors_strength} + + Слабые стороны + {competitors_low} + + Наши преимущества + {advantages} + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide7.tsx b/src/pages/deck/document/slides/Slide7.tsx new file mode 100644 index 0000000..8d6ef31 --- /dev/null +++ b/src/pages/deck/document/slides/Slide7.tsx @@ -0,0 +1,59 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; + +export interface Slide7Props { + data: ExtractArray['data']; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + marginBottom: 24 + }, + subtitle: { + width: '100%', + fontSize: '24px', + }, +}); + +export const Slide7: ReactFCC = (props) => { + const { data } = props; + + const money = data.find((i) => i.slug === 'money')?.answer as string; + + return ( + + + Бизнес-модель + + + {money} + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide8.tsx b/src/pages/deck/document/slides/Slide8.tsx new file mode 100644 index 0000000..dcd0033 --- /dev/null +++ b/src/pages/deck/document/slides/Slide8.tsx @@ -0,0 +1,63 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; + +export interface Slide8Props { + data: ExtractArray['data']; + financialChart: string; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + marginBottom: 24 + }, + image: { + maxWidth: '600px', + maxHeight: '300px', + margin: '0 auto' + } +}); + +export const Slide8: ReactFCC = (props) => { + const { data, financialChart } = props; + + const how_much_investments = data.find((i) => i.slug === 'how_much_investments')?.answer.sum as any; + const financial_indicators = data.find((i) => i.slug === 'financial_indicators')?.answer as any; + const users_metrics = data.find((i) => i.slug === 'users_metrics')?.answer as any; + + return ( + + + Финансы + + {financial_indicators} + + + + ) +} \ No newline at end of file diff --git a/src/pages/deck/document/slides/Slide9.tsx b/src/pages/deck/document/slides/Slide9.tsx new file mode 100644 index 0000000..0551071 --- /dev/null +++ b/src/pages/deck/document/slides/Slide9.tsx @@ -0,0 +1,55 @@ +import {ReactFCC} from '../../../../utils/ReactFCC'; +import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer'; +import {bgColor, primaryColor} from '../shared'; +import {GetDeckResponse} from '../../../../api/deck/getDeck'; +import {ExtractArray} from '../../../../utils/types'; + +export interface Slide9Props { + data: ExtractArray['data']; +} + +const styles = StyleSheet.create({ + page: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + backgroundColor: bgColor, + color: primaryColor, + fontFamily: 'Roboto', + padding: '48px', + }, + title: { + fontSize: 36, + width: '100%', + letterSpacing: 3, + textTransform: 'uppercase' + }, + divider: { + width: '100%', + height: 1, + backgroundColor: primaryColor, + margin: '8px 0 16px' + }, + text: { + width: '100%', + fontSize: '20px', + marginBottom: 24 + }, +}); + +export const Slide9: ReactFCC = (props) => { + const { data } = props; + + // const how_much_investments = data.find((i) => i.slug === 'how_much_investments')?.answer as any; + // const users_metrics = data.find((i) => i.slug === 'users_metrics')?.answer as any; + + return ( + + + Команда + {/**/} + {/*{how_much_investments}*/} + + + ) +} \ No newline at end of file diff --git a/src/utils/types.ts b/src/utils/types.ts index de4c21e..19722fd 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -9,6 +9,8 @@ export type IntrinsicPropsWithoutRef = Re JSX.IntrinsicElements[E] >; +export type ExtractArray = T extends (infer U)[] ? U : T; + export type AnchorPropsWithoutRef = IntrinsicPropsWithoutRef<'a'>; export type DivPropsWithoutRef = IntrinsicPropsWithoutRef<'div'>; export type PPropsWithoutRef = IntrinsicPropsWithoutRef<'p'>; diff --git a/yarn.lock b/yarn.lock index 95ff16f..f9a6165 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1606,6 +1606,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@kurkle/color@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f" + integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -3380,6 +3385,13 @@ char-regex@^2.0.0: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e" integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw== +chart.js@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.0.tgz#df843fdd9ec6bd88d7f07e2b95348d221bd2698c" + integrity sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ== + dependencies: + "@kurkle/color" "^0.3.0" + check-types@^11.1.1: version "11.2.2" resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.2.tgz#7afc0b6a860d686885062f2dba888ba5710335b4"