From 6ae2fb07fc267f0b09960b12a3fcc4333c1cbcbb Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:28:50 +0300 Subject: [PATCH] Feature/profile reorganise (#196) * Profile reorganise --- public/locales/en/translation.json | 9 +- public/locales/ru/translation.json | 11 +- .../Article/AudioPlayer/AudioPlayer.tsx | 3 +- .../{ => AuthorCard}/AuthorCard.module.scss | 22 +++ .../Author/{ => AuthorCard}/AuthorCard.tsx | 178 +++++++++++++++--- src/components/Author/AuthorCard/index.ts | 1 + src/components/Author/Full.module.scss | 9 - src/components/Author/Full.tsx | 12 -- .../Views/Author/Author.module.scss | 50 ----- src/components/Views/Author/Author.tsx | 132 ++----------- src/components/Views/Feed.tsx | 7 +- .../_shared/SolidSwiper/SolidSwiper.tsx | 5 +- src/pages/author.page.tsx | 8 +- src/pages/topic.page.tsx | 3 +- src/stores/ui.ts | 6 +- src/stores/zine/articles.ts | 21 --- src/stores/zine/authors.ts | 20 +- src/stores/zine/topics.ts | 24 +-- src/utils/getNumeralsDeclension.ts | 3 + src/utils/handleFileUpload.ts | 3 +- 20 files changed, 245 insertions(+), 282 deletions(-) rename src/components/Author/{ => AuthorCard}/AuthorCard.module.scss (95%) rename src/components/Author/{ => AuthorCard}/AuthorCard.tsx (56%) create mode 100644 src/components/Author/AuthorCard/index.ts delete mode 100644 src/components/Author/Full.module.scss delete mode 100644 src/components/Author/Full.tsx create mode 100644 src/utils/getNumeralsDeclension.ts diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 2203af75..83ff92f5 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -393,5 +393,12 @@ "user already exist": "user already exists", "video": "video", "view": "view", - "zine": "zine" + "zine": "zine", + "subscriber": "subscriber", + "subscriber_rp": "subscriber", + "subscribers": "subscribers", + "subscription": "subscription", + "subscription_rp": "subscription", + "subscriptions": "subscriptions", + "Users": "Users" } diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 91037269..96559f15 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -300,6 +300,7 @@ "Subscribe what you like to tune your personal feed": "Подпишитесь на интересующие вас темы, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях", "Subscribe who you like to tune your personal feed": "Подпишитесь на интересующих вас авторов, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях", "Subscription": "Подписка", + "subscription": "подписка", "Subscriptions": "Подписки", "Substrate": "Подложка", "Success": "Успешно", @@ -387,7 +388,6 @@ "email not confirmed": "email не подтвержден", "enter": "войдите", "feed": "лента", - "follower": "подписчик", "general feed": "Общая лента", "header 1": "заголовок 1", "header 2": "заголовок 2", @@ -419,5 +419,12 @@ "video": "видео", "view": "просмотр", "zine": "журнал", - "Enter footnote text": "Введите текст сноски" + "Enter footnote text": "Введите текст сноски", + "follower": "подписчик", + "subscriber": "подписчик", + "subscriber_rp": "подписчика", + "subscribers": "подписчиков", + "subscription_rp": "подписки", + "subscriptions": "подписок", + "Users": "Пользователи" } diff --git a/src/components/Article/AudioPlayer/AudioPlayer.tsx b/src/components/Article/AudioPlayer/AudioPlayer.tsx index 3954965d..265f55d3 100644 --- a/src/components/Article/AudioPlayer/AudioPlayer.tsx +++ b/src/components/Article/AudioPlayer/AudioPlayer.tsx @@ -35,7 +35,8 @@ export const AudioPlayer = (props: Props) => { () => currentTrackIndex(), () => { setCurrentTrackDuration(0) - } + }, + { defer: true } ) ) diff --git a/src/components/Author/AuthorCard.module.scss b/src/components/Author/AuthorCard/AuthorCard.module.scss similarity index 95% rename from src/components/Author/AuthorCard.module.scss rename to src/components/Author/AuthorCard/AuthorCard.module.scss index 69e39749..460be48a 100644 --- a/src/components/Author/AuthorCard.module.scss +++ b/src/components/Author/AuthorCard/AuthorCard.module.scss @@ -441,3 +441,25 @@ width: 1.6rem; } } + +.subscribers { + cursor: pointer; + display: inline-flex; + vertical-align: top; + align-items: center; + + .userpic { + background: var(--background-color); + box-shadow: 0 0 0 2px var(--background-color); + vertical-align: top; + + &:not(:first-child) { + margin-left: -2.2rem; + } + } +} + +.listWrapper { + overflow: auto; + max-height: 70vh; +} diff --git a/src/components/Author/AuthorCard.tsx b/src/components/Author/AuthorCard/AuthorCard.tsx similarity index 56% rename from src/components/Author/AuthorCard.tsx rename to src/components/Author/AuthorCard/AuthorCard.tsx index 306b62b6..d73f3022 100644 --- a/src/components/Author/AuthorCard.tsx +++ b/src/components/Author/AuthorCard/AuthorCard.tsx @@ -1,20 +1,25 @@ -import type { Author } from '../../graphql/types.gen' -import { Userpic } from './Userpic' -import { Icon } from '../_shared/Icon' +import type { Author } from '../../../graphql/types.gen' +import { Userpic } from '../Userpic' +import { Icon } from '../../_shared/Icon' import styles from './AuthorCard.module.scss' -import { createMemo, createSignal, For, Show } from 'solid-js' -import { translit } from '../../utils/ru2en' -import { follow, unfollow } from '../../stores/zine/common' +import { createEffect, createMemo, createSignal, For, Show } from 'solid-js' +import { translit } from '../../../utils/ru2en' +import { follow, unfollow } from '../../../stores/zine/common' import { clsx } from 'clsx' -import { useSession } from '../../context/session' -import { StatMetrics } from '../_shared/StatMetrics' -import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient' -import { FollowingEntity } from '../../graphql/types.gen' -import { router, useRouter } from '../../stores/router' +import { useSession } from '../../../context/session' +import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient' +import { FollowingEntity, Topic } from '../../../graphql/types.gen' +import { router, useRouter } from '../../../stores/router' import { openPage } from '@nanostores/router' -import { useLocalize } from '../../context/localize' +import { useLocalize } from '../../../context/localize' +import { ConditionalWrapper } from '../../_shared/ConditionalWrapper' +import { Modal } from '../../Nav/Modal' +import { showModal } from '../../../stores/ui' +import { TopicCard } from '../../Topic/Card' +import { getNumeralsDeclension } from '../../../utils/getNumeralsDeclension' -interface AuthorCardProps { +type SubscriptionFilter = 'all' | 'users' | 'topics' +type AuthorCardProps = { caption?: string hideWriteButton?: boolean hideDescription?: boolean @@ -32,6 +37,12 @@ interface AuthorCardProps { isFeedMode?: boolean isNowrap?: boolean class?: string + followers?: Author[] + subscriptions?: Array +} + +function isAuthor(value: Author | Topic): value is Author { + return 'name' in value } export const AuthorCard = (props: AuthorCardProps) => { @@ -44,6 +55,8 @@ export const AuthorCard = (props: AuthorCardProps) => { } = useSession() const [isSubscribing, setIsSubscribing] = createSignal(false) + const [subscriptions, setSubscriptions] = createSignal>(props.subscriptions) + const [subscriptionFilter, setSubscriptionFilter] = createSignal('all') const subscribed = createMemo(() => { return session()?.news?.authors?.some((u) => u === props.author.slug) || false @@ -89,6 +102,18 @@ export const AuthorCard = (props: AuthorCardProps) => { }, 'subscribe') } + createEffect(() => { + if (props.subscriptions) { + if (subscriptionFilter() === 'users') { + setSubscriptions(props.subscriptions.filter((s) => 'name' in s)) + } else if (subscriptionFilter() === 'topics') { + setSubscriptions(props.subscriptions.filter((s) => 'title' in s)) + } else { + setSubscriptions(props.subscriptions) + } + } + }) + return (
{ }} >
- - - - -
{name()}
-
+
+ ( + + {children} + + )} + > + {name()} + +
- - {props.isAuthorsList} +
{ /> - - + 0}> +
showModal('followers')}> + + {(f) => } + +
+ {props.followers.length}  + {getNumeralsDeclension(props.followers.length, [ + t('subscriber'), + t('subscriber_rp'), + t('subscribers') + ])} +
+
+
+ 0}> +
+
showModal('subscriptions')}> + + {(f) => { + if ('name' in f) { + return + } else if ('title' in f) { + return + } + return null + }} + +
+ {props.subscriptions.length}  + {getNumeralsDeclension(props.subscriptions.length, [ + t('subscription'), + t('subscription_rp'), + t('subscriptions') + ])} +
+
+
@@ -256,6 +318,68 @@ export const AuthorCard = (props: AuthorCardProps) => {
+ + + <> +

{t('Followers')}

+
+
+ + {(follower: Author) => ( +
+ +
+ )} +
+
+
+ +
+
+ + + <> +

{t('Subscriptions')}

+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+ + {(subscription: Author | Topic) => ( +
+ {isAuthor(subscription) ? ( + + ) : ( + + )} +
+ )} +
+
+
+ +
+
) } diff --git a/src/components/Author/AuthorCard/index.ts b/src/components/Author/AuthorCard/index.ts new file mode 100644 index 00000000..ceaabe6d --- /dev/null +++ b/src/components/Author/AuthorCard/index.ts @@ -0,0 +1 @@ +export { AuthorCard } from './AuthorCard' diff --git a/src/components/Author/Full.module.scss b/src/components/Author/Full.module.scss deleted file mode 100644 index 0fe5e7bc..00000000 --- a/src/components/Author/Full.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -.userDetails { - border-bottom: 2px solid #000; - margin: 0 0 3.6rem; - padding-bottom: 3.6rem; - - @include media-breakpoint-down(md) { - text-align: center; - } -} diff --git a/src/components/Author/Full.tsx b/src/components/Author/Full.tsx deleted file mode 100644 index 73395e37..00000000 --- a/src/components/Author/Full.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { Author } from '../../graphql/types.gen' -import { AuthorCard } from './AuthorCard' -import styles from './Full.module.scss' -import clsx from 'clsx' - -export const AuthorFull = (props: { author: Author }) => { - return ( -
- -
- ) -} diff --git a/src/components/Views/Author/Author.module.scss b/src/components/Views/Author/Author.module.scss index 3cfd2bbc..6f176203 100644 --- a/src/components/Views/Author/Author.module.scss +++ b/src/components/Views/Author/Author.module.scss @@ -34,56 +34,6 @@ } } -.userpic { - background: #fff; - box-shadow: 0 0 0 2px #fff; - display: inline-block; - margin-right: -1.2rem; - vertical-align: top; -} - -.subscribers { - cursor: pointer; - display: inline-block; - margin: -0.4rem 2em 0 0; - vertical-align: top; -} - -.subscribersCounter { - @include font-size(1rem); - background: #fff; - border: 2px solid #000; - border-radius: 100%; - font-weight: bold; - height: 32px; - line-height: 30px; - position: relative; - text-align: center; - width: 32px; - z-index: 1; -} - -.subscribersList { - max-height: 15em; - overflow: auto; - position: relative; - - .subscriber { - white-space: nowrap; - display: flex; - flex-direction: row; - align-items: center; - margin: 0; - border-radius: 4px; - padding: 8px 4px; - transition: background 0.2s ease-in-out; - - &:hover { - background: #f7f7f7; - } - } -} - .loadingWrapper { position: relative; min-height: 40vh; diff --git a/src/components/Views/Author/Author.tsx b/src/components/Views/Author/Author.tsx index adb986d0..d7e6f1e8 100644 --- a/src/components/Views/Author/Author.tsx +++ b/src/components/Views/Author/Author.tsx @@ -3,7 +3,6 @@ import type { Author, Shout, Topic } from '../../../graphql/types.gen' import { Row1 } from '../../Feed/Row1' import { Row2 } from '../../Feed/Row2' import { Row3 } from '../../Feed/Row3' -import { AuthorFull } from '../../Author/Full' import { useAuthorsStore } from '../../../stores/zine/authors' import { loadShouts, useArticlesStore } from '../../../stores/zine/articles' @@ -13,8 +12,6 @@ import { splitToPages } from '../../../utils/splitToPages' import styles from './Author.module.scss' import stylesArticle from '../../Article/Article.module.scss' import { clsx } from 'clsx' -import { Userpic } from '../../Author/Userpic' -import { Popup } from '../../_shared/Popup' import { AuthorCard } from '../../Author/AuthorCard' import { apiClient } from '../../../utils/apiClient' import { Comment } from '../../Article/Comment' @@ -22,6 +19,7 @@ import { useLocalize } from '../../../context/localize' import { AuthorRatingControl } from '../../Author/AuthorRatingControl' import { TopicCard } from '../../Topic/Card' import { Loading } from '../../_shared/Loading' +import { hideModal } from '../../../stores/ui' type AuthorProps = { shouts: Shout[] @@ -30,24 +28,12 @@ type AuthorProps = { } export type AuthorPageSearchParams = { - by: - | '' - | 'viewed' - | 'rating' - | 'commented' - | 'recent' - | 'subscriptions' - | 'followers' - | 'about' - | 'popular' + by: '' | 'viewed' | 'rating' | 'commented' | 'recent' | 'about' | 'popular' } export const PRERENDERED_ARTICLES_COUNT = 12 const LOAD_MORE_PAGE_SIZE = 9 -function isAuthor(value: Author | Topic): value is Author { - return 'name' in value -} export const AuthorView = (props: AuthorProps) => { const { t } = useLocalize() const { sortedArticles } = useArticlesStore({ shouts: props.shouts }) @@ -59,7 +45,6 @@ export const AuthorView = (props: AuthorProps) => { const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [followers, setFollowers] = createSignal([]) const [subscriptions, setSubscriptions] = createSignal>([]) - const [isLoaded, setIsLoaded] = createSignal() const fetchSubscriptions = async (): Promise<{ authors: Author[]; topics: Topic[] }> => { try { @@ -77,6 +62,7 @@ export const AuthorView = (props: AuthorProps) => { } onMount(async () => { + hideModal() try { const userSubscribers = await apiClient.getAuthorFollowers({ slug: props.authorSlug }) setFollowers(userSubscribers) @@ -89,6 +75,8 @@ export const AuthorView = (props: AuthorProps) => { if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) { await loadMore() } + const { authors, topics } = await fetchSubscriptions() + setSubscriptions([...authors, ...topics]) }) const loadMore = async () => { @@ -118,13 +106,6 @@ export const AuthorView = (props: AuthorProps) => { const [commented, setCommented] = createSignal([]) createEffect(async () => { - if (searchParams().by === 'subscriptions') { - setIsLoaded(false) - const { authors, topics } = await fetchSubscriptions() - setSubscriptions([...authors, ...topics]) - setIsLoaded(true) - } - if (searchParams().by === 'commented') { try { const data = await apiClient.getReactionsBy({ @@ -140,7 +121,12 @@ export const AuthorView = (props: AuthorProps) => {
- +
@@ -150,16 +136,6 @@ export const AuthorView = (props: AuthorProps) => { {t('Publications')} -
  • - -
  • -
  • - -
  • - - - - - {(f) => } - - - 3}> - - {(f) => } - -
    - {followers().length} -
    -
    -
    -
    - } - variant="tiny" - > -
      - - {(item: Author) => ( -
    • - -
    • - )} -
      -
    - -
    {t('Karma')} @@ -239,52 +176,7 @@ export const AuthorView = (props: AuthorProps) => {
    - -
    -
    - - {(follower: Author) => ( -
    - -
    - )} -
    -
    -
    -
    - -
    -
    - - -
    - } - > - - {(subscription: Author | Topic) => ( -
    - {isAuthor(subscription) ? ( -
    - -
    - ) : ( - - )} -
    - )} -
    - -
    -
    - + diff --git a/src/components/Views/Feed.tsx b/src/components/Views/Feed.tsx index c763f9da..a37dd66a 100644 --- a/src/components/Views/Feed.tsx +++ b/src/components/Views/Feed.tsx @@ -55,13 +55,18 @@ export const FeedView = () => { actions: { loadReactionsBy } } = useReactions() + onMount(() => { + loadMore() + }) + createEffect( on( () => page().route + searchParams().by, () => { resetSortedArticles() loadMore() - } + }, + { defer: true } ) ) diff --git a/src/components/_shared/SolidSwiper/SolidSwiper.tsx b/src/components/_shared/SolidSwiper/SolidSwiper.tsx index 4417dd06..0c670873 100644 --- a/src/components/_shared/SolidSwiper/SolidSwiper.tsx +++ b/src/components/_shared/SolidSwiper/SolidSwiper.tsx @@ -63,7 +63,8 @@ export const SolidSwiper = (props: Props) => { () => { mainSwipeRef.current?.swiper.update() thumbSwipeRef.current?.swiper.update() - } + }, + { defer: true } ) ) @@ -95,7 +96,7 @@ export const SolidSwiper = (props: Props) => { const results: UploadedFile[] = [] for (const file of selectedFiles) { const result = await handleFileUpload(file) - results.push(result.url) + results.push(result) } props.onImagesAdd(composeMediaItems(results)) setLoading(false) diff --git a/src/pages/author.page.tsx b/src/pages/author.page.tsx index 7346f789..c0533f19 100644 --- a/src/pages/author.page.tsx +++ b/src/pages/author.page.tsx @@ -17,14 +17,15 @@ export const AuthorPage = (props: PageProps) => { Boolean(props.authorShouts) && Boolean(props.author) && props.author.slug === slug() ) - const preload = () => - Promise.all([ + const preload = () => { + return Promise.all([ loadShouts({ filters: { author: slug(), visibility: 'community' }, limit: PRERENDERED_ARTICLES_COUNT }), loadAuthor({ slug: slug() }) ]) + } onMount(async () => { if (isLoaded()) { @@ -44,7 +45,8 @@ export const AuthorPage = (props: PageProps) => { resetSortedArticles() await preload() setIsLoaded(true) - } + }, + { defer: true } ) ) diff --git a/src/pages/topic.page.tsx b/src/pages/topic.page.tsx index c1f3d3c9..8988a719 100644 --- a/src/pages/topic.page.tsx +++ b/src/pages/topic.page.tsx @@ -41,7 +41,8 @@ export const TopicPage = (props: PageProps) => { resetSortedArticles() await preload() setIsLoaded(true) - } + }, + { defer: true } ) ) diff --git a/src/stores/ui.ts b/src/stores/ui.ts index b01bd52d..1477d57d 100644 --- a/src/stores/ui.ts +++ b/src/stores/ui.ts @@ -20,6 +20,8 @@ export type ModalType = | 'uploadCoverImage' | 'editorInsertLink' | 'simplifiedEditorInsertLink' + | 'followers' + | 'subscriptions' type WarnKind = 'error' | 'warn' | 'info' @@ -41,7 +43,9 @@ export const MODALS: Record = { simplifiedEditorUploadImage: 'simplifiedEditorUploadImage', uploadCoverImage: 'uploadCoverImage', editorInsertLink: 'editorInsertLink', - simplifiedEditorInsertLink: 'simplifiedEditorInsertLink' + simplifiedEditorInsertLink: 'simplifiedEditorInsertLink', + followers: 'followers', + subscriptions: 'subscriptions' } const [modal, setModal] = createSignal(null) diff --git a/src/stores/zine/articles.ts b/src/stores/zine/articles.ts index b333aa99..6335513b 100644 --- a/src/stores/zine/articles.ts +++ b/src/stores/zine/articles.ts @@ -1,7 +1,6 @@ import type { Author, Shout, ShoutInput, Topic, LoadShoutsOptions } from '../../graphql/types.gen' import { apiClient } from '../../utils/apiClient' import { addAuthorsByTopic } from './authors' -import { addTopicsByAuthor } from './topics' import { byStat } from '../../utils/sortby' import { createSignal } from 'solid-js' import { createLazyMemo } from '@solid-primitives/memo' @@ -97,26 +96,6 @@ const addArticles = (...args: Shout[][]) => { }, {} as { [topicSlug: string]: Author[] }) addAuthorsByTopic(authorsByTopic) - - const topicsByAuthor = allArticles.reduce((acc, article) => { - const { authors, topics } = article - - authors.forEach((author) => { - if (!acc[author.slug]) { - acc[author.slug] = [] - } - - topics.forEach((topic) => { - if (!acc[author.slug].some((t) => t.slug === topic.slug)) { - acc[author.slug].push(topic) - } - }) - }) - - return acc - }, {} as { [authorSlug: string]: Topic[] }) - - addTopicsByAuthor(topicsByAuthor) } const addSortedArticles = (articles: Shout[]) => { diff --git a/src/stores/zine/authors.ts b/src/stores/zine/authors.ts index c133b8ec..df3f3385 100644 --- a/src/stores/zine/authors.ts +++ b/src/stores/zine/authors.ts @@ -1,6 +1,6 @@ import { apiClient } from '../../utils/apiClient' import type { Author } from '../../graphql/types.gen' -import { createSignal } from 'solid-js' +import { createEffect, createSignal } from 'solid-js' import { createLazyMemo } from '@solid-primitives/memo' import { byStat } from '../../utils/sortby' @@ -38,12 +38,18 @@ const addAuthors = (authors: Author[]) => { return acc }, {} as Record) - setAuthorEntities((prevAuthorEntities) => { - return { - ...prevAuthorEntities, - ...newAuthorEntities - } - }) + setAuthorEntities((prevAuthorEntities) => + Object.keys(newAuthorEntities).reduce( + (acc, authorSlug) => { + acc[authorSlug] = { + ...acc[authorSlug], + ...newAuthorEntities[authorSlug] + } + return acc + }, + { ...prevAuthorEntities } + ) + ) } export const loadAuthor = async ({ slug }: { slug: string }): Promise => { diff --git a/src/stores/zine/topics.ts b/src/stores/zine/topics.ts index 568695b0..c8d81caf 100644 --- a/src/stores/zine/topics.ts +++ b/src/stores/zine/topics.ts @@ -12,7 +12,6 @@ export const setTopicsSort = (sortBy: TopicsSortBy) => setSortAllBy(sortBy) const [topicEntities, setTopicEntities] = createSignal<{ [topicSlug: string]: Topic }>({}) const [randomTopics, setRandomTopics] = createSignal([]) -const [topicsByAuthor, setTopicByAuthor] = createSignal<{ [authorSlug: string]: Topic[] }>({}) const sortedTopics = createLazyMemo(() => { const topics = Object.values(topicEntities()) @@ -68,27 +67,6 @@ const addTopics = (...args: Topic[][]) => { }) } -export const addTopicsByAuthor = (newTopicsByAuthors: { [authorSlug: string]: Topic[] }) => { - const allTopics = Object.values(newTopicsByAuthors).flat() - addTopics(allTopics) - - setTopicByAuthor((prevTopicsByAuthor) => { - return Object.entries(newTopicsByAuthors).reduce((acc, [authorSlug, topics]) => { - if (!acc[authorSlug]) { - acc[authorSlug] = [] - } - - topics.forEach((topic) => { - if (!acc[authorSlug].some((t) => t.slug === topic.slug)) { - acc[authorSlug].push(topic) - } - }) - - return acc - }, prevTopicsByAuthor) - }) -} - export const loadAllTopics = async (): Promise => { const topics = await apiClient.getAllTopics() addTopics(topics) @@ -121,5 +99,5 @@ export const useTopicsStore = (initialState: InitialState = {}) => { setRandomTopics(initialState.randomTopics) } - return { topicEntities, sortedTopics, randomTopics, topTopics, topicsByAuthor } + return { topicEntities, sortedTopics, randomTopics, topTopics } } diff --git a/src/utils/getNumeralsDeclension.ts b/src/utils/getNumeralsDeclension.ts new file mode 100644 index 00000000..dafc15c0 --- /dev/null +++ b/src/utils/getNumeralsDeclension.ts @@ -0,0 +1,3 @@ +// Usage in tsx: {getNumeralsDeclension(NUMBER, ['яблоко', 'яблока', 'яблок'])} +export const getNumeralsDeclension = (number: number, words: string[], cases = [2, 0, 1, 1, 1, 2]) => + words[number % 100 > 4 && number % 100 < 20 ? 2 : cases[number % 10 < 5 ? number % 10 : 5]] diff --git a/src/utils/handleFileUpload.ts b/src/utils/handleFileUpload.ts index c9c925d4..ddf5d4ef 100644 --- a/src/utils/handleFileUpload.ts +++ b/src/utils/handleFileUpload.ts @@ -1,9 +1,10 @@ import { UploadFile } from '@solid-primitives/upload' import { apiBaseUrl } from './config' +import { UploadedFile } from '../pages/types' const apiUrl = `${apiBaseUrl}/upload` -export const handleFileUpload = async (uploadFile: UploadFile) => { +export const handleFileUpload = async (uploadFile: UploadFile): Promise => { const formData = new FormData() formData.append('file', uploadFile.file, uploadFile.name) const response = await fetch(apiUrl, {