From 96d6e6bd0c18e59fdd612de91024f049b8a7c1af Mon Sep 17 00:00:00 2001 From: ilya-bkv Date: Mon, 11 Mar 2024 10:41:31 +0300 Subject: [PATCH 01/10] Fix login password onBlur --- src/components/Nav/AuthModal/LoginForm.tsx | 2 +- .../Nav/AuthModal/PasswordField/PasswordField.tsx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/Nav/AuthModal/LoginForm.tsx b/src/components/Nav/AuthModal/LoginForm.tsx index 4ea5a5f6..1c4c774d 100644 --- a/src/components/Nav/AuthModal/LoginForm.tsx +++ b/src/components/Nav/AuthModal/LoginForm.tsx @@ -156,7 +156,7 @@ export const LoginForm = () => { handlePasswordInput(value)} + onBlur={(value) => handlePasswordInput(value)} /> diff --git a/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx b/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx index bf7f00b1..a478225d 100644 --- a/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx +++ b/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx @@ -12,7 +12,7 @@ type Props = { placeholder?: string errorMessage?: (error: string) => void setError?: string - onInput: (value: string) => void + onInput?: (value: string) => void onBlur?: (value: string) => void variant?: 'login' | 'registration' disableAutocomplete?: boolean @@ -41,8 +41,9 @@ export const PasswordField = (props: Props) => { } const handleInputBlur = (value: string) => { - if (props.variant === 'login') { - return props.onBlur(value) + if (props.variant === 'login' && props.onBlur) { + props.onBlur(value) + return } if (value.length < 1) { return From 36120abda5d1b2914fd6ed5728246318de5d9f7b Mon Sep 17 00:00:00 2001 From: Untone Date: Mon, 11 Mar 2024 16:23:06 +0300 Subject: [PATCH 02/10] validation-fix --- src/components/Nav/AuthModal/LoginForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Nav/AuthModal/LoginForm.tsx b/src/components/Nav/AuthModal/LoginForm.tsx index 1c4c774d..98c9ec05 100644 --- a/src/components/Nav/AuthModal/LoginForm.tsx +++ b/src/components/Nav/AuthModal/LoginForm.tsx @@ -160,7 +160,7 @@ export const LoginForm = () => { /> -
{submitError()}
+
{submitError()}
From 4fdd025e44406f76ee5a453f4040158f9fdb0702 Mon Sep 17 00:00:00 2001 From: ilya-bkv Date: Mon, 18 Mar 2024 10:22:12 +0300 Subject: [PATCH 03/10] [FIX] Show more shouts --- src/components/Views/Home.tsx | 1 - src/stores/zine/articles.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Views/Home.tsx b/src/components/Views/Home.tsx index 4e4dbce3..5161f75e 100644 --- a/src/components/Views/Home.tsx +++ b/src/components/Views/Home.tsx @@ -64,7 +64,6 @@ export const HomeView = (props: Props) => { limit: CLIENT_LOAD_ARTICLES_COUNT, offset: sortedArticles().length, }) - setIsLoadMoreButtonVisible(hasMore) } diff --git a/src/stores/zine/articles.ts b/src/stores/zine/articles.ts index e517378b..a52abf65 100644 --- a/src/stores/zine/articles.ts +++ b/src/stores/zine/articles.ts @@ -131,9 +131,8 @@ export const loadShout = async (slug: string): Promise => { export const loadShouts = async ( options: LoadShoutsOptions, ): Promise<{ hasMore: boolean; newShouts: Shout[] }> => { - options.limit += 1 const newShouts = await apiClient.getShouts(options) - const hasMore = newShouts?.length === options.limit + 1 + const hasMore = newShouts?.length !== options.limit + 1 && newShouts?.length !== 0 if (hasMore) { newShouts.splice(-1) From 896c180dd1e4d00ae5e14d97085491ba604cadcd Mon Sep 17 00:00:00 2001 From: ilya-bkv Date: Mon, 18 Mar 2024 10:40:31 +0300 Subject: [PATCH 04/10] [FIX] Show reaction after submit --- src/graphql/client/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index 4cbe81c2..ee76347b 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -182,7 +182,7 @@ export const apiClient = { createReaction: async (input: ReactionInput) => { const response = await apiClient.private.mutation(reactionCreate, { reaction: input }).toPromise() console.debug('[graphql.client.core] createReaction:', response) - return response.data.create_reaction.reaction + return response.data.create_reaction }, destroyReaction: async (reaction_id: number) => { const response = await apiClient.private.mutation(reactionDestroy, { reaction_id }).toPromise() From 6812ecd1873d33a981af474d6099f218362bb5ba Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Mon, 18 Mar 2024 14:07:28 +0300 Subject: [PATCH 05/10] One request for random topics (#428) --- package-lock.json | 6 +++ package.json | 1 + src/components/App.tsx | 33 ++++++++------- src/components/Nav/Header/Header.tsx | 15 +++---- src/components/Views/Home.tsx | 9 ---- src/context/topics.tsx | 59 +++++++++++++++++++++++++++ src/utils/getRandomTopicsFromArray.ts | 7 ++++ 7 files changed, 99 insertions(+), 31 deletions(-) create mode 100644 src/context/topics.tsx create mode 100644 src/utils/getRandomTopicsFromArray.ts diff --git a/package-lock.json b/package-lock.json index e8a84240..5c76496f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "license": "MIT", "dependencies": { "form-data": "4.0.0", + "idb": "8.0.0", "mailgun.js": "10.1.0" }, "devDependencies": { @@ -7231,6 +7232,11 @@ "node": ">=0.10.0" } }, + "node_modules/idb": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz", + "integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", diff --git a/package.json b/package.json index 9a277dbc..5e79d11f 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "form-data": "4.0.0", + "idb": "8.0.0", "mailgun.js": "10.1.0" }, "devDependencies": { diff --git a/src/components/App.tsx b/src/components/App.tsx index c595ec15..15ce90bf 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -14,6 +14,7 @@ import { MediaQueryProvider } from '../context/mediaQuery' import { NotificationsProvider } from '../context/notifications' import { SessionProvider } from '../context/session' import { SnackbarProvider } from '../context/snackbar' +import { TopicsProvider } from '../context/topics' import { DiscussionRulesPage } from '../pages/about/discussionRules.page' import { DogmaPage } from '../pages/about/dogma.page' import { GuidePage } from '../pages/about/guide.page' @@ -116,21 +117,23 @@ export const App = (props: Props) => { - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/components/Nav/Header/Header.tsx b/src/components/Nav/Header/Header.tsx index dfbb2e71..7c0258e1 100644 --- a/src/components/Nav/Header/Header.tsx +++ b/src/components/Nav/Header/Header.tsx @@ -6,12 +6,10 @@ import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid import { useLocalize } from '../../../context/localize' import { useSession } from '../../../context/session' -import { apiClient } from '../../../graphql/client/core' import { ROUTES, router, useRouter } from '../../../stores/router' import { useModalStore } from '../../../stores/ui' import { getDescription } from '../../../utils/meta' import { SharePopup, getShareUrl } from '../../Article/SharePopup' -import { RANDOM_TOPICS_COUNT } from '../../Views/Home' import { Icon } from '../../_shared/Icon' import { Subscribe } from '../../_shared/Subscribe' import { AuthModal } from '../AuthModal' @@ -23,6 +21,8 @@ import { Snackbar } from '../Snackbar' import { Link } from './Link' +import { useTopics } from '../../../context/topics' +import { getRandomTopicsFromArray } from '../../../utils/getRandomTopicsFromArray' import styles from './Header.module.scss' type Props = { @@ -48,6 +48,7 @@ export const Header = (props: Props) => { const { page } = useRouter() const { requireAuthentication } = useSession() const { searchParams } = useRouter() + const { topics } = useTopics() const [randomTopics, setRandomTopics] = createSignal([]) const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false) const [getIsScrolled, setIsScrolled] = createSignal(false) @@ -58,6 +59,7 @@ export const Header = (props: Props) => { const [isTopicsVisible, setIsTopicsVisible] = createSignal(false) const [isZineVisible, setIsZineVisible] = createSignal(false) const [isFeedVisible, setIsFeedVisible] = createSignal(false) + const toggleFixed = () => setFixed(!fixed()) const tag = (topic: Topic) => @@ -65,6 +67,10 @@ export const Header = (props: Props) => { let windowScrollTop = 0 + createEffect(() => { + setRandomTopics(getRandomTopicsFromArray(topics())) + }) + createEffect(() => { const mainContent = document.querySelector('.main-content') @@ -141,11 +147,6 @@ export const Header = (props: Props) => { }, time) } - onMount(async () => { - const topics = await apiClient.getRandomTopics({ amount: RANDOM_TOPICS_COUNT }) - setRandomTopics(topics) - }) - const handleToggleMenuByLink = (event: MouseEvent, route: keyof typeof ROUTES) => { if (!fixed()) { return diff --git a/src/components/Views/Home.tsx b/src/components/Views/Home.tsx index 5161f75e..25e15a31 100644 --- a/src/components/Views/Home.tsx +++ b/src/components/Views/Home.tsx @@ -66,15 +66,6 @@ export const HomeView = (props: Props) => { }) setIsLoadMoreButtonVisible(hasMore) } - - const result = await apiClient.getRandomTopicShouts(RANDOM_TOPIC_SHOUTS_COUNT) - if (!result || result.error) console.warn('[apiClient.getRandomTopicShouts] failed') - batch(() => { - if (!result?.error) { - if (result?.topic) setRandomTopic(result.topic) - if (result?.shouts) setRandomTopicArticles(result.shouts) - } - }) }) const loadMore = async () => { diff --git a/src/context/topics.tsx b/src/context/topics.tsx new file mode 100644 index 00000000..4fe8d5f6 --- /dev/null +++ b/src/context/topics.tsx @@ -0,0 +1,59 @@ +import { openDB } from 'idb' +import { Accessor, JSX, createContext, createSignal, onMount, useContext } from 'solid-js' +import { apiClient } from '../graphql/client/core' +import { Topic } from '../graphql/schema/core.gen' + +type TopicsContextType = { + topics: Accessor +} + +const TopicsContext = createContext() +export function useTopics() { + return useContext(TopicsContext) +} + +const DB_NAME = 'discourseAppDB' +const DB_VERSION = 1 +const STORE_NAME = 'topics' +const setupIndexedDB = async () => { + return await openDB(DB_NAME, DB_VERSION, { + upgrade(db) { + if (!db.objectStoreNames.contains(STORE_NAME)) { + db.createObjectStore(STORE_NAME, { keyPath: 'id' }) + } + }, + }) +} + +const getTopicsFromIndexedDB = async (db) => { + const tx = db.transaction(STORE_NAME, 'readonly') + const store = tx.objectStore(STORE_NAME) + return store.getAll() +} +const saveTopicsToIndexedDB = async (db, topics) => { + const tx = db.transaction(STORE_NAME, 'readwrite') + const store = tx.objectStore(STORE_NAME) + for (const topic of topics) { + await store.put(topic) + } + await tx.done +} + +export const TopicsProvider = (props: { children: JSX.Element }) => { + const [randomTopics, setRandomTopics] = createSignal([]) + + onMount(async () => { + const db = await setupIndexedDB() + let topics = await getTopicsFromIndexedDB(db) + + if (topics.length === 0) { + topics = await apiClient.getAllTopics() + await saveTopicsToIndexedDB(db, topics) + } + setRandomTopics(topics) + }) + + const value: TopicsContextType = { topics: randomTopics } + + return {props.children} +} diff --git a/src/utils/getRandomTopicsFromArray.ts b/src/utils/getRandomTopicsFromArray.ts new file mode 100644 index 00000000..ced616c0 --- /dev/null +++ b/src/utils/getRandomTopicsFromArray.ts @@ -0,0 +1,7 @@ +import { RANDOM_TOPICS_COUNT } from '../components/Views/Home' +import { Topic } from '../graphql/schema/core.gen' + +export const getRandomTopicsFromArray = (topics: Topic[], count: number = RANDOM_TOPICS_COUNT): Topic[] => { + const shuffledTopics = [...topics].sort(() => 0.5 - Math.random()) + return shuffledTopics.slice(0, count) +} From 084bd29d2b1b7778eedf4a1bae549e29ed4889c6 Mon Sep 17 00:00:00 2001 From: ilya-bkv Date: Mon, 18 Mar 2024 14:22:10 +0300 Subject: [PATCH 06/10] Change Expo article's count --- src/components/Views/Expo/Expo.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Views/Expo/Expo.tsx b/src/components/Views/Expo/Expo.tsx index 6dbe995b..e4541922 100644 --- a/src/components/Views/Expo/Expo.tsx +++ b/src/components/Views/Expo/Expo.tsx @@ -24,8 +24,8 @@ type Props = { layout: LayoutType } -export const PRERENDERED_ARTICLES_COUNT = 24 -const LOAD_MORE_PAGE_SIZE = 16 +export const PRERENDERED_ARTICLES_COUNT = 36 +const LOAD_MORE_PAGE_SIZE = 12 export const Expo = (props: Props) => { const [isLoaded, setIsLoaded] = createSignal(Boolean(props.shouts)) From 96685507eadf1f2d9ccbda5821a48ab41f6ccce6 Mon Sep 17 00:00:00 2001 From: ilya-bkv Date: Mon, 18 Mar 2024 14:25:10 +0300 Subject: [PATCH 07/10] Fix response Update reaction --- src/graphql/client/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index ee76347b..36ae8f70 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -192,7 +192,7 @@ export const apiClient = { updateReaction: async (reaction: ReactionInput) => { const response = await apiClient.private.mutation(reactionUpdate, { reaction }).toPromise() console.debug('[graphql.client.core] updateReaction:', response) - return response.data.update_reaction.reaction + return response.data.update_reaction }, loadAuthorsBy: async (args: QueryLoad_Authors_ByArgs) => { const resp = await publicGraphQLClient.query(authorsLoadBy, args).toPromise() From 546d3d2659f32a2d76d353d7bd8094dff3951fde Mon Sep 17 00:00:00 2001 From: ilya-bkv Date: Mon, 18 Mar 2024 14:55:07 +0300 Subject: [PATCH 08/10] Fix userpic upload mutation --- .../ProfileSettings/ProfileSettings.tsx | 2 +- src/context/profile.tsx | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/components/ProfileSettings/ProfileSettings.tsx b/src/components/ProfileSettings/ProfileSettings.tsx index 1c2afd98..70389ccb 100644 --- a/src/components/ProfileSettings/ProfileSettings.tsx +++ b/src/components/ProfileSettings/ProfileSettings.tsx @@ -123,7 +123,7 @@ export const ProfileSettings = () => { setIsUserpicUpdating(true) const result = await handleImageUpload(uploadFile) - updateFormField('userpic', result.url) + updateFormField('pic', result.url) setUserpicFile(null) setIsUserpicUpdating(false) diff --git a/src/context/profile.tsx b/src/context/profile.tsx index 1b88f149..48262694 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -54,18 +54,12 @@ export const ProfileFormProvider = (props: { children: JSX.Element }) => { const updateFormField = (fieldName: string, value: string, remove?: boolean) => { if (fieldName === 'links') { - if (remove) { - setForm( - 'links', - form.links.filter((item) => item !== value), - ) - } else { - setForm((prev) => ({ ...prev, links: [...prev.links, value] })) - } + setForm((prev) => { + const updatedLinks = remove ? prev.links.filter((item) => item !== value) : [...prev.links, value]; + return { ...prev, links: updatedLinks }; + }); } else { - setForm({ - [fieldName]: value, - }) + setForm((prev) => ({ ...prev, [fieldName]: value })); } } From a88d200109988e626791e628706f916cdd8a15d9 Mon Sep 17 00:00:00 2001 From: ilya-bkv Date: Mon, 18 Mar 2024 14:57:55 +0300 Subject: [PATCH 09/10] Biome fix --- src/context/profile.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/context/profile.tsx b/src/context/profile.tsx index 48262694..775b55c0 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -55,11 +55,11 @@ export const ProfileFormProvider = (props: { children: JSX.Element }) => { const updateFormField = (fieldName: string, value: string, remove?: boolean) => { if (fieldName === 'links') { setForm((prev) => { - const updatedLinks = remove ? prev.links.filter((item) => item !== value) : [...prev.links, value]; - return { ...prev, links: updatedLinks }; - }); + const updatedLinks = remove ? prev.links.filter((item) => item !== value) : [...prev.links, value] + return { ...prev, links: updatedLinks } + }) } else { - setForm((prev) => ({ ...prev, [fieldName]: value })); + setForm((prev) => ({ ...prev, [fieldName]: value })) } } From 5f939839fb8bdeb1532946772a6b579354b351e2 Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 19 Mar 2024 14:23:20 +0300 Subject: [PATCH 10/10] public-env-urls --- src/graphql/client/chat.ts | 3 ++- src/graphql/client/core.ts | 5 +++-- src/graphql/createGraphQLClient.ts | 9 ++++----- src/utils/config.ts | 9 +++++++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/graphql/client/chat.ts b/src/graphql/client/chat.ts index 42c5be97..df801c02 100644 --- a/src/graphql/client/chat.ts +++ b/src/graphql/client/chat.ts @@ -21,10 +21,11 @@ import { QueryLoad_ChatsArgs, QueryLoad_Messages_ByArgs, } from '../schema/chat.gen' +import { chatApiUrl } from '../../utils/config' export const inboxClient = { private: null, - connect: (token: string) => (inboxClient.private = createGraphQLClient('chat', token)), + connect: (token: string) => (inboxClient.private = createGraphQLClient(chatApiUrl, token)), loadChats: async (options: QueryLoad_ChatsArgs): Promise => { const resp = await inboxClient.private.query(myChats, options).toPromise() diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index 36ae8f70..bb68d943 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -46,14 +46,15 @@ import reactionsLoadBy from '../query/core/reactions-load-by' import topicBySlug from '../query/core/topic-by-slug' import topicsAll from '../query/core/topics-all' import topicsRandomQuery from '../query/core/topics-random' +import { coreApiUrl } from "../../utils/config" -const publicGraphQLClient = createGraphQLClient('core') +const publicGraphQLClient = createGraphQLClient(coreApiUrl) export const apiClient = { private: null, connect: (token: string) => { // NOTE: use it after token appears - apiClient.private = createGraphQLClient('core', token) + apiClient.private = createGraphQLClient(coreApiUrl, token) }, getRandomTopShouts: async (params: QueryLoad_Shouts_Random_TopArgs) => { diff --git a/src/graphql/createGraphQLClient.ts b/src/graphql/createGraphQLClient.ts index 62755e9a..89b0d620 100644 --- a/src/graphql/createGraphQLClient.ts +++ b/src/graphql/createGraphQLClient.ts @@ -1,18 +1,17 @@ -import { ClientOptions, Exchange, createClient, dedupExchange, fetchExchange } from '@urql/core' +import { ClientOptions, Exchange, createClient, fetchExchange } from '@urql/core' import { devtoolsExchange } from '@urql/devtools' import { isDev } from '../utils/config' -const exchanges: Exchange[] = [dedupExchange, fetchExchange] +const exchanges: Exchange[] = [fetchExchange] if (isDev) { exchanges.unshift(devtoolsExchange) } -export const createGraphQLClient = (serviceName: string, token = '') => { +export const createGraphQLClient = (url: string, token = '') => { const options: ClientOptions = { - url: `https://${serviceName}.discours.io`, - maskTypename: true, + url, requestPolicy: 'cache-and-network', fetchOptions: () => (token ? { headers: { Authorization: token } } : {}), exchanges, diff --git a/src/utils/config.ts b/src/utils/config.ts index 1cfd89a5..9585180d 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -8,3 +8,12 @@ export const SENTRY_DSN = import.meta.env.PUBLIC_SENTRY_DSN || '' const defaultSearchUrl = 'https://search.discours.io' export const searchUrl = import.meta.env.PUBLIC_SEARCH_URL || defaultSearchUrl + +const defaultCoreUrl = 'https://core.discours.io' +export const coreApiUrl = import.meta.env.PUBLIC_CORE_API || defaultCoreUrl + +const defaultChatUrl = 'https://chat.discours.io' +export const chatApiUrl = import.meta.env.PUBLIC_CHAT_API || defaultChatUrl + +const defaultAuthUrl = 'https://auth.discours.io' +export const authApiUrl = import.meta.env.PUBLIC_AUTH_API || defaultAuthUrl \ No newline at end of file