Merge branch 'dev' into feature/rating
This commit is contained in:
commit
d36603a57c
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -11,6 +11,7 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
|
"idb": "8.0.0",
|
||||||
"mailgun.js": "10.1.0"
|
"mailgun.js": "10.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -7231,6 +7232,11 @@
|
||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
|
"idb": "8.0.0",
|
||||||
"mailgun.js": "10.1.0"
|
"mailgun.js": "10.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { MediaQueryProvider } from '../context/mediaQuery'
|
||||||
import { NotificationsProvider } from '../context/notifications'
|
import { NotificationsProvider } from '../context/notifications'
|
||||||
import { SessionProvider } from '../context/session'
|
import { SessionProvider } from '../context/session'
|
||||||
import { SnackbarProvider } from '../context/snackbar'
|
import { SnackbarProvider } from '../context/snackbar'
|
||||||
|
import { TopicsProvider } from '../context/topics'
|
||||||
import { DiscussionRulesPage } from '../pages/about/discussionRules.page'
|
import { DiscussionRulesPage } from '../pages/about/discussionRules.page'
|
||||||
import { DogmaPage } from '../pages/about/dogma.page'
|
import { DogmaPage } from '../pages/about/dogma.page'
|
||||||
import { GuidePage } from '../pages/about/guide.page'
|
import { GuidePage } from '../pages/about/guide.page'
|
||||||
|
@ -116,21 +117,23 @@ export const App = (props: Props) => {
|
||||||
<LocalizeProvider>
|
<LocalizeProvider>
|
||||||
<MediaQueryProvider>
|
<MediaQueryProvider>
|
||||||
<SnackbarProvider>
|
<SnackbarProvider>
|
||||||
<ConfirmProvider>
|
<TopicsProvider>
|
||||||
<SessionProvider onStateChangeCallback={console.log}>
|
<ConfirmProvider>
|
||||||
<FollowingProvider>
|
<SessionProvider onStateChangeCallback={console.log}>
|
||||||
<ConnectProvider>
|
<FollowingProvider>
|
||||||
<NotificationsProvider>
|
<ConnectProvider>
|
||||||
<EditorProvider>
|
<NotificationsProvider>
|
||||||
<InboxProvider>
|
<EditorProvider>
|
||||||
<Dynamic component={pageComponent()} {...props} />
|
<InboxProvider>
|
||||||
</InboxProvider>
|
<Dynamic component={pageComponent()} {...props} />
|
||||||
</EditorProvider>
|
</InboxProvider>
|
||||||
</NotificationsProvider>
|
</EditorProvider>
|
||||||
</ConnectProvider>
|
</NotificationsProvider>
|
||||||
</FollowingProvider>
|
</ConnectProvider>
|
||||||
</SessionProvider>
|
</FollowingProvider>
|
||||||
</ConfirmProvider>
|
</SessionProvider>
|
||||||
|
</ConfirmProvider>
|
||||||
|
</TopicsProvider>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
</MediaQueryProvider>
|
</MediaQueryProvider>
|
||||||
</LocalizeProvider>
|
</LocalizeProvider>
|
||||||
|
|
|
@ -156,7 +156,7 @@ export const LoginForm = () => {
|
||||||
<PasswordField
|
<PasswordField
|
||||||
variant={'login'}
|
variant={'login'}
|
||||||
setError={validationErrors().password}
|
setError={validationErrors().password}
|
||||||
onInput={(value) => handlePasswordInput(value)}
|
onBlur={(value) => handlePasswordInput(value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Show when={submitError()}>
|
<Show when={submitError()}>
|
||||||
|
|
|
@ -12,7 +12,7 @@ type Props = {
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
errorMessage?: (error: string) => void
|
errorMessage?: (error: string) => void
|
||||||
setError?: string
|
setError?: string
|
||||||
onInput: (value: string) => void
|
onInput?: (value: string) => void
|
||||||
onBlur?: (value: string) => void
|
onBlur?: (value: string) => void
|
||||||
variant?: 'login' | 'registration'
|
variant?: 'login' | 'registration'
|
||||||
disableAutocomplete?: boolean
|
disableAutocomplete?: boolean
|
||||||
|
@ -41,8 +41,9 @@ export const PasswordField = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleInputBlur = (value: string) => {
|
const handleInputBlur = (value: string) => {
|
||||||
if (props.variant === 'login') {
|
if (props.variant === 'login' && props.onBlur) {
|
||||||
return props.onBlur(value)
|
props.onBlur(value)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (value.length < 1) {
|
if (value.length < 1) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -6,12 +6,10 @@ import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { apiClient } from '../../../graphql/client/core'
|
|
||||||
import { ROUTES, router, useRouter } from '../../../stores/router'
|
import { ROUTES, router, useRouter } from '../../../stores/router'
|
||||||
import { useModalStore } from '../../../stores/ui'
|
import { useModalStore } from '../../../stores/ui'
|
||||||
import { getDescription } from '../../../utils/meta'
|
import { getDescription } from '../../../utils/meta'
|
||||||
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
|
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
|
||||||
import { RANDOM_TOPICS_COUNT } from '../../Views/Home'
|
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Subscribe } from '../../_shared/Subscribe'
|
import { Subscribe } from '../../_shared/Subscribe'
|
||||||
import { AuthModal } from '../AuthModal'
|
import { AuthModal } from '../AuthModal'
|
||||||
|
@ -23,6 +21,8 @@ import { Snackbar } from '../Snackbar'
|
||||||
|
|
||||||
import { Link } from './Link'
|
import { Link } from './Link'
|
||||||
|
|
||||||
|
import { useTopics } from '../../../context/topics'
|
||||||
|
import { getRandomTopicsFromArray } from '../../../utils/getRandomTopicsFromArray'
|
||||||
import styles from './Header.module.scss'
|
import styles from './Header.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -48,6 +48,7 @@ export const Header = (props: Props) => {
|
||||||
const { page } = useRouter()
|
const { page } = useRouter()
|
||||||
const { requireAuthentication } = useSession()
|
const { requireAuthentication } = useSession()
|
||||||
const { searchParams } = useRouter<HeaderSearchParams>()
|
const { searchParams } = useRouter<HeaderSearchParams>()
|
||||||
|
const { topics } = useTopics()
|
||||||
const [randomTopics, setRandomTopics] = createSignal([])
|
const [randomTopics, setRandomTopics] = createSignal([])
|
||||||
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
|
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
|
||||||
const [getIsScrolled, setIsScrolled] = createSignal(false)
|
const [getIsScrolled, setIsScrolled] = createSignal(false)
|
||||||
|
@ -58,6 +59,7 @@ export const Header = (props: Props) => {
|
||||||
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
|
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
|
||||||
const [isZineVisible, setIsZineVisible] = createSignal(false)
|
const [isZineVisible, setIsZineVisible] = createSignal(false)
|
||||||
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
|
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
|
||||||
|
|
||||||
const toggleFixed = () => setFixed(!fixed())
|
const toggleFixed = () => setFixed(!fixed())
|
||||||
|
|
||||||
const tag = (topic: Topic) =>
|
const tag = (topic: Topic) =>
|
||||||
|
@ -65,6 +67,10 @@ export const Header = (props: Props) => {
|
||||||
|
|
||||||
let windowScrollTop = 0
|
let windowScrollTop = 0
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
setRandomTopics(getRandomTopicsFromArray(topics()))
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const mainContent = document.querySelector<HTMLDivElement>('.main-content')
|
const mainContent = document.querySelector<HTMLDivElement>('.main-content')
|
||||||
|
|
||||||
|
@ -141,11 +147,6 @@ export const Header = (props: Props) => {
|
||||||
}, time)
|
}, time)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const topics = await apiClient.getRandomTopics({ amount: RANDOM_TOPICS_COUNT })
|
|
||||||
setRandomTopics(topics)
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleToggleMenuByLink = (event: MouseEvent, route: keyof typeof ROUTES) => {
|
const handleToggleMenuByLink = (event: MouseEvent, route: keyof typeof ROUTES) => {
|
||||||
if (!fixed()) {
|
if (!fixed()) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -123,7 +123,7 @@ export const ProfileSettings = () => {
|
||||||
setIsUserpicUpdating(true)
|
setIsUserpicUpdating(true)
|
||||||
|
|
||||||
const result = await handleImageUpload(uploadFile)
|
const result = await handleImageUpload(uploadFile)
|
||||||
updateFormField('userpic', result.url)
|
updateFormField('pic', result.url)
|
||||||
|
|
||||||
setUserpicFile(null)
|
setUserpicFile(null)
|
||||||
setIsUserpicUpdating(false)
|
setIsUserpicUpdating(false)
|
||||||
|
|
|
@ -24,8 +24,8 @@ type Props = {
|
||||||
layout: LayoutType
|
layout: LayoutType
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PRERENDERED_ARTICLES_COUNT = 24
|
export const PRERENDERED_ARTICLES_COUNT = 36
|
||||||
const LOAD_MORE_PAGE_SIZE = 16
|
const LOAD_MORE_PAGE_SIZE = 12
|
||||||
|
|
||||||
export const Expo = (props: Props) => {
|
export const Expo = (props: Props) => {
|
||||||
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.shouts))
|
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.shouts))
|
||||||
|
|
|
@ -64,18 +64,8 @@ export const HomeView = (props: Props) => {
|
||||||
limit: CLIENT_LOAD_ARTICLES_COUNT,
|
limit: CLIENT_LOAD_ARTICLES_COUNT,
|
||||||
offset: sortedArticles().length,
|
offset: sortedArticles().length,
|
||||||
})
|
})
|
||||||
|
|
||||||
setIsLoadMoreButtonVisible(hasMore)
|
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 () => {
|
const loadMore = async () => {
|
||||||
|
|
|
@ -54,18 +54,12 @@ export const ProfileFormProvider = (props: { children: JSX.Element }) => {
|
||||||
|
|
||||||
const updateFormField = (fieldName: string, value: string, remove?: boolean) => {
|
const updateFormField = (fieldName: string, value: string, remove?: boolean) => {
|
||||||
if (fieldName === 'links') {
|
if (fieldName === 'links') {
|
||||||
if (remove) {
|
setForm((prev) => {
|
||||||
setForm(
|
const updatedLinks = remove ? prev.links.filter((item) => item !== value) : [...prev.links, value]
|
||||||
'links',
|
return { ...prev, links: updatedLinks }
|
||||||
form.links.filter((item) => item !== value),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
setForm((prev) => ({ ...prev, links: [...prev.links, value] }))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setForm({
|
|
||||||
[fieldName]: value,
|
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
setForm((prev) => ({ ...prev, [fieldName]: value }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
59
src/context/topics.tsx
Normal file
59
src/context/topics.tsx
Normal file
|
@ -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<Topic[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
const TopicsContext = createContext<TopicsContextType>()
|
||||||
|
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<Topic[]>([])
|
||||||
|
|
||||||
|
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 <TopicsContext.Provider value={value}>{props.children}</TopicsContext.Provider>
|
||||||
|
}
|
|
@ -21,10 +21,11 @@ import {
|
||||||
QueryLoad_ChatsArgs,
|
QueryLoad_ChatsArgs,
|
||||||
QueryLoad_Messages_ByArgs,
|
QueryLoad_Messages_ByArgs,
|
||||||
} from '../schema/chat.gen'
|
} from '../schema/chat.gen'
|
||||||
|
import { chatApiUrl } from '../../utils/config'
|
||||||
|
|
||||||
export const inboxClient = {
|
export const inboxClient = {
|
||||||
private: null,
|
private: null,
|
||||||
connect: (token: string) => (inboxClient.private = createGraphQLClient('chat', token)),
|
connect: (token: string) => (inboxClient.private = createGraphQLClient(chatApiUrl, token)),
|
||||||
|
|
||||||
loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => {
|
loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => {
|
||||||
const resp = await inboxClient.private.query(myChats, options).toPromise()
|
const resp = await inboxClient.private.query(myChats, options).toPromise()
|
||||||
|
|
|
@ -46,14 +46,15 @@ import reactionsLoadBy from '../query/core/reactions-load-by'
|
||||||
import topicBySlug from '../query/core/topic-by-slug'
|
import topicBySlug from '../query/core/topic-by-slug'
|
||||||
import topicsAll from '../query/core/topics-all'
|
import topicsAll from '../query/core/topics-all'
|
||||||
import topicsRandomQuery from '../query/core/topics-random'
|
import topicsRandomQuery from '../query/core/topics-random'
|
||||||
|
import { coreApiUrl } from "../../utils/config"
|
||||||
|
|
||||||
const publicGraphQLClient = createGraphQLClient('core')
|
const publicGraphQLClient = createGraphQLClient(coreApiUrl)
|
||||||
|
|
||||||
export const apiClient = {
|
export const apiClient = {
|
||||||
private: null,
|
private: null,
|
||||||
connect: (token: string) => {
|
connect: (token: string) => {
|
||||||
// NOTE: use it after token appears
|
// NOTE: use it after token appears
|
||||||
apiClient.private = createGraphQLClient('core', token)
|
apiClient.private = createGraphQLClient(coreApiUrl, token)
|
||||||
},
|
},
|
||||||
|
|
||||||
getRandomTopShouts: async (params: QueryLoad_Shouts_Random_TopArgs) => {
|
getRandomTopShouts: async (params: QueryLoad_Shouts_Random_TopArgs) => {
|
||||||
|
@ -192,7 +193,7 @@ export const apiClient = {
|
||||||
updateReaction: async (reaction: ReactionInput) => {
|
updateReaction: async (reaction: ReactionInput) => {
|
||||||
const response = await apiClient.private.mutation(reactionUpdate, { reaction }).toPromise()
|
const response = await apiClient.private.mutation(reactionUpdate, { reaction }).toPromise()
|
||||||
console.debug('[graphql.client.core] updateReaction:', response)
|
console.debug('[graphql.client.core] updateReaction:', response)
|
||||||
return response.data.update_reaction.reaction
|
return response.data.update_reaction
|
||||||
},
|
},
|
||||||
loadAuthorsBy: async (args: QueryLoad_Authors_ByArgs) => {
|
loadAuthorsBy: async (args: QueryLoad_Authors_ByArgs) => {
|
||||||
const resp = await publicGraphQLClient.query(authorsLoadBy, args).toPromise()
|
const resp = await publicGraphQLClient.query(authorsLoadBy, args).toPromise()
|
||||||
|
|
|
@ -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 { devtoolsExchange } from '@urql/devtools'
|
||||||
|
|
||||||
import { isDev } from '../utils/config'
|
import { isDev } from '../utils/config'
|
||||||
|
|
||||||
const exchanges: Exchange[] = [dedupExchange, fetchExchange]
|
const exchanges: Exchange[] = [fetchExchange]
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
exchanges.unshift(devtoolsExchange)
|
exchanges.unshift(devtoolsExchange)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createGraphQLClient = (serviceName: string, token = '') => {
|
export const createGraphQLClient = (url: string, token = '') => {
|
||||||
const options: ClientOptions = {
|
const options: ClientOptions = {
|
||||||
url: `https://${serviceName}.discours.io`,
|
url,
|
||||||
maskTypename: true,
|
|
||||||
requestPolicy: 'cache-and-network',
|
requestPolicy: 'cache-and-network',
|
||||||
fetchOptions: () => (token ? { headers: { Authorization: token } } : {}),
|
fetchOptions: () => (token ? { headers: { Authorization: token } } : {}),
|
||||||
exchanges,
|
exchanges,
|
||||||
|
|
|
@ -131,9 +131,8 @@ export const loadShout = async (slug: string): Promise<void> => {
|
||||||
export const loadShouts = async (
|
export const loadShouts = async (
|
||||||
options: LoadShoutsOptions,
|
options: LoadShoutsOptions,
|
||||||
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
|
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
|
||||||
options.limit += 1
|
|
||||||
const newShouts = await apiClient.getShouts(options)
|
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) {
|
if (hasMore) {
|
||||||
newShouts.splice(-1)
|
newShouts.splice(-1)
|
||||||
|
|
|
@ -8,3 +8,12 @@ export const SENTRY_DSN = import.meta.env.PUBLIC_SENTRY_DSN || ''
|
||||||
|
|
||||||
const defaultSearchUrl = 'https://search.discours.io'
|
const defaultSearchUrl = 'https://search.discours.io'
|
||||||
export const searchUrl = import.meta.env.PUBLIC_SEARCH_URL || defaultSearchUrl
|
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
|
7
src/utils/getRandomTopicsFromArray.ts
Normal file
7
src/utils/getRandomTopicsFromArray.ts
Normal file
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user