auth-graphql-removed
This commit is contained in:
parent
fecce70aaa
commit
a703b85bd8
22
codegen.yml
22
codegen.yml
|
@ -36,17 +36,17 @@ generates:
|
||||||
useTypeImports: true
|
useTypeImports: true
|
||||||
outputPath: './src/graphql/types/notifier.gen.ts'
|
outputPath: './src/graphql/types/notifier.gen.ts'
|
||||||
|
|
||||||
# Generate types for auth
|
# internal types for auth
|
||||||
src/graphql/schema/auth.gen.ts:
|
# src/graphql/schema/auth.gen.ts:
|
||||||
schema: 'https://auth.discours.io/graphql'
|
# schema: 'https://auth.discours.io/graphql'
|
||||||
plugins:
|
# plugins:
|
||||||
- 'typescript'
|
# - 'typescript'
|
||||||
- 'typescript-operations'
|
# - 'typescript-operations'
|
||||||
- 'typescript-urql'
|
# - 'typescript-urql'
|
||||||
config:
|
# config:
|
||||||
skipTypename: true
|
# skipTypename: true
|
||||||
useTypeImports: true
|
# useTypeImports: true
|
||||||
outputPath: './src/graphql/types/auth.gen.ts'
|
# outputPath: './src/graphql/types/auth.gen.ts'
|
||||||
|
|
||||||
hooks:
|
hooks:
|
||||||
afterAllFileWrite:
|
afterAllFileWrite:
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Component, createEffect, createMemo } from 'solid-js'
|
||||||
import { Dynamic } from 'solid-js/web'
|
import { Dynamic } from 'solid-js/web'
|
||||||
|
|
||||||
import { ConfirmProvider } from '../context/confirm'
|
import { ConfirmProvider } from '../context/confirm'
|
||||||
|
import { ConnectProvider } from '../context/connect'
|
||||||
import { EditorProvider } from '../context/editor'
|
import { EditorProvider } from '../context/editor'
|
||||||
import { LocalizeProvider } from '../context/localize'
|
import { LocalizeProvider } from '../context/localize'
|
||||||
import { NotificationsProvider } from '../context/notifications'
|
import { NotificationsProvider } from '../context/notifications'
|
||||||
|
@ -40,7 +41,6 @@ import { SearchPage } from '../pages/search.page'
|
||||||
import { TopicPage } from '../pages/topic.page'
|
import { TopicPage } from '../pages/topic.page'
|
||||||
import { ROUTES, useRouter } from '../stores/router'
|
import { ROUTES, useRouter } from '../stores/router'
|
||||||
import { hideModal, MODALS, showModal } from '../stores/ui'
|
import { hideModal, MODALS, showModal } from '../stores/ui'
|
||||||
import { ConnectProvider } from '../context/connect'
|
|
||||||
|
|
||||||
// TODO: lazy load
|
// TODO: lazy load
|
||||||
// const SomePage = lazy(() => import('./Pages/SomePage'))
|
// const SomePage = lazy(() => import('./Pages/SomePage'))
|
||||||
|
|
|
@ -6,13 +6,14 @@ import { createMemo, createSignal, onMount, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useNotifications } from '../../../context/notifications'
|
import { useNotifications } from '../../../context/notifications'
|
||||||
|
import { apiClient } from '../../../graphql/client/core'
|
||||||
|
import { Reaction, Shout } from '../../../graphql/schema/core.gen'
|
||||||
import { Notification } from '../../../graphql/schema/notifier.gen'
|
import { Notification } from '../../../graphql/schema/notifier.gen'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
import { GroupAvatar } from '../../_shared/GroupAvatar'
|
import { GroupAvatar } from '../../_shared/GroupAvatar'
|
||||||
import { TimeAgo } from '../../_shared/TimeAgo'
|
import { TimeAgo } from '../../_shared/TimeAgo'
|
||||||
|
|
||||||
import styles from './NotificationView.module.scss'
|
import styles from './NotificationView.module.scss'
|
||||||
import { apiClient } from '../../../graphql/client/core'
|
|
||||||
import { Reaction, Shout } from '../../../graphql/schema/core.gen'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notification: Notification
|
notification: Notification
|
||||||
|
@ -111,19 +112,20 @@ export const NotificationView = (props: Props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'update': {
|
case 'update':
|
||||||
}
|
|
||||||
case 'delete': {
|
case 'delete':
|
||||||
}
|
|
||||||
case 'follow': {
|
case 'follow':
|
||||||
}
|
|
||||||
case 'unfollow': {
|
case 'unfollow':
|
||||||
}
|
|
||||||
case 'invited': {
|
case 'invited':
|
||||||
// TODO: invited for collaborative authoring
|
// TODO: invited for collaborative authoring
|
||||||
}
|
|
||||||
default:
|
default: {
|
||||||
return <></>
|
return <></>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,47 +1,14 @@
|
||||||
import { createContext, createEffect, createMemo, onCleanup, onMount, useContext } from 'solid-js'
|
|
||||||
import type { ParentComponent } from 'solid-js'
|
import type { ParentComponent } from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
|
||||||
import { Authorizer, User, AuthToken } from '@authorizerdev/authorizer-js'
|
|
||||||
|
|
||||||
export enum AuthorizerProviderActionType {
|
import { Authorizer, User, AuthToken, ConfigType } from '@authorizerdev/authorizer-js'
|
||||||
SET_USER = 'SET_USER',
|
import { createContext, createEffect, createMemo, onCleanup, onMount, useContext } from 'solid-js'
|
||||||
SET_TOKEN = 'SET_TOKEN',
|
import { createStore } from 'solid-js/store'
|
||||||
SET_LOADING = 'SET_LOADING',
|
|
||||||
SET_AUTH_DATA = 'SET_AUTH_DATA',
|
|
||||||
SET_CONFIG = 'SET_CONFIG',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthorizerState = {
|
export type AuthorizerState = {
|
||||||
user: User | null
|
user: User | null
|
||||||
token: AuthToken | null
|
token: AuthToken | null
|
||||||
loading: boolean
|
loading: boolean
|
||||||
config: {
|
config: ConfigType
|
||||||
authorizerURL: string
|
|
||||||
redirectURL: string
|
|
||||||
client_id: string
|
|
||||||
is_google_login_enabled: boolean
|
|
||||||
is_github_login_enabled: boolean
|
|
||||||
is_facebook_login_enabled: boolean
|
|
||||||
is_linkedin_login_enabled: boolean
|
|
||||||
is_apple_login_enabled: boolean
|
|
||||||
is_twitter_login_enabled: boolean
|
|
||||||
is_microsoft_login_enabled: boolean
|
|
||||||
is_email_verification_enabled: boolean
|
|
||||||
is_basic_authentication_enabled: boolean
|
|
||||||
is_magic_link_login_enabled: boolean
|
|
||||||
is_sign_up_enabled: boolean
|
|
||||||
is_strong_password_enabled: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthorizerProviderAction = {
|
|
||||||
type: AuthorizerProviderActionType
|
|
||||||
payload: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OtpDataType = {
|
|
||||||
isScreenVisible: boolean
|
|
||||||
email: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthorizerContextActions = {
|
type AuthorizerContextActions = {
|
||||||
|
@ -52,27 +19,15 @@ type AuthorizerContextActions = {
|
||||||
authorizer: () => Authorizer
|
authorizer: () => Authorizer
|
||||||
logout: () => Promise<void>
|
logout: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
const config: ConfigType = {
|
||||||
|
authorizerURL: 'http://auth.discours.io',
|
||||||
|
redirectURL: 'http://auth.discours.io',
|
||||||
|
clientID: '9c113377-5eea-4c89-98e1-69302462fc08', // FIXME: use env?
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: fix types
|
|
||||||
const AuthorizerContext = createContext<[AuthorizerState, AuthorizerContextActions]>([
|
const AuthorizerContext = createContext<[AuthorizerState, AuthorizerContextActions]>([
|
||||||
{
|
{
|
||||||
config: {
|
config,
|
||||||
authorizerURL: '',
|
|
||||||
redirectURL: '/',
|
|
||||||
client_id: '',
|
|
||||||
is_google_login_enabled: true,
|
|
||||||
is_github_login_enabled: true,
|
|
||||||
is_facebook_login_enabled: true,
|
|
||||||
is_linkedin_login_enabled: false,
|
|
||||||
is_apple_login_enabled: false,
|
|
||||||
is_twitter_login_enabled: true,
|
|
||||||
is_microsoft_login_enabled: false,
|
|
||||||
is_email_verification_enabled: true,
|
|
||||||
is_basic_authentication_enabled: true,
|
|
||||||
is_magic_link_login_enabled: true,
|
|
||||||
is_sign_up_enabled: true,
|
|
||||||
is_strong_password_enabled: true,
|
|
||||||
},
|
|
||||||
user: null,
|
user: null,
|
||||||
token: null,
|
token: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -82,12 +37,7 @@ const AuthorizerContext = createContext<[AuthorizerState, AuthorizerContextActio
|
||||||
setToken: () => {},
|
setToken: () => {},
|
||||||
setUser: () => {},
|
setUser: () => {},
|
||||||
setAuthData: () => {},
|
setAuthData: () => {},
|
||||||
authorizer: () =>
|
authorizer: () => new Authorizer(config),
|
||||||
new Authorizer({
|
|
||||||
authorizerURL: 'http://auth.discours.io',
|
|
||||||
redirectURL: 'http://auth.discours.io',
|
|
||||||
clientID: '', // FIXME: add real client id
|
|
||||||
}),
|
|
||||||
logout: async () => {},
|
logout: async () => {},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
@ -106,21 +56,8 @@ export const AuthorizerProvider: ParentComponent<AuthorizerProviderProps> = (pro
|
||||||
loading: true,
|
loading: true,
|
||||||
config: {
|
config: {
|
||||||
authorizerURL: props.authorizerURL,
|
authorizerURL: props.authorizerURL,
|
||||||
is_apple_login_enabled: false,
|
clientID: props.clientID,
|
||||||
is_microsoft_login_enabled: false,
|
} as ConfigType,
|
||||||
redirectURL: props.redirectURL,
|
|
||||||
is_google_login_enabled: true,
|
|
||||||
is_github_login_enabled: true,
|
|
||||||
is_facebook_login_enabled: true,
|
|
||||||
is_linkedin_login_enabled: false,
|
|
||||||
is_twitter_login_enabled: true,
|
|
||||||
is_email_verification_enabled: true,
|
|
||||||
is_basic_authentication_enabled: true,
|
|
||||||
is_magic_link_login_enabled: true,
|
|
||||||
is_sign_up_enabled: false,
|
|
||||||
is_strong_password_enabled: true,
|
|
||||||
client_id: props.clientID,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const authorizer = createMemo(
|
const authorizer = createMemo(
|
||||||
|
@ -202,7 +139,7 @@ export const AuthorizerProvider: ParentComponent<AuthorizerProviderProps> = (pro
|
||||||
} else {
|
} else {
|
||||||
setState((prev) => ({ ...prev, user: null, token: null }))
|
setState((prev) => ({ ...prev, user: null, token: null }))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
setState((prev) => ({ ...prev, user: null, token: null }))
|
setState((prev) => ({ ...prev, user: null, token: null }))
|
||||||
} finally {
|
} finally {
|
||||||
setState('config', (config) => ({ ...config, ...metaRes }))
|
setState('config', (config) => ({ ...config, ...metaRes }))
|
||||||
|
|
|
@ -2,7 +2,9 @@ import type { Accessor, JSX } from 'solid-js'
|
||||||
|
|
||||||
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
||||||
import { createContext, useContext, createSignal, createEffect } from 'solid-js'
|
import { createContext, useContext, createSignal, createEffect } from 'solid-js'
|
||||||
|
|
||||||
import { getToken } from '../graphql/privateGraphQLClient'
|
import { getToken } from '../graphql/privateGraphQLClient'
|
||||||
|
|
||||||
import { useSession } from './session'
|
import { useSession } from './session'
|
||||||
|
|
||||||
export interface SSEMessage {
|
export interface SSEMessage {
|
||||||
|
@ -28,7 +30,7 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
|
||||||
// const [messages, setMessages] = createSignal<Array<SSEMessage>>([]);
|
// const [messages, setMessages] = createSignal<Array<SSEMessage>>([]);
|
||||||
|
|
||||||
const [connected, setConnected] = createSignal(false)
|
const [connected, setConnected] = createSignal(false)
|
||||||
const { isAuthenticated, author } = useSession()
|
const { isAuthenticated } = useSession()
|
||||||
|
|
||||||
const addHandler = (handler: MessageHandler) => {
|
const addHandler = (handler: MessageHandler) => {
|
||||||
setHandlers((hhh) => [...hhh, handler])
|
setHandlers((hhh) => [...hhh, handler])
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/schema/chat.gen'
|
import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/schema/chat.gen'
|
||||||
import type { Accessor, JSX } from 'solid-js'
|
import type { Accessor, JSX } from 'solid-js'
|
||||||
|
|
||||||
import { createContext, createSignal, useContext } from 'solid-js'
|
import { createContext, createSignal, useContext } from 'solid-js'
|
||||||
import { SSEMessage, useConnect } from './connect'
|
|
||||||
import { loadMessages } from '../stores/inbox'
|
|
||||||
import { inboxClient } from '../graphql/client/chat'
|
import { inboxClient } from '../graphql/client/chat'
|
||||||
|
import { loadMessages } from '../stores/inbox'
|
||||||
|
|
||||||
|
import { SSEMessage, useConnect } from './connect'
|
||||||
|
|
||||||
type InboxContextType = {
|
type InboxContextType = {
|
||||||
chats: Accessor<Chat[]>
|
chats: Accessor<Chat[]>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated'
|
||||||
import { NotificationsPanel } from '../components/NotificationsPanel'
|
import { NotificationsPanel } from '../components/NotificationsPanel'
|
||||||
import { notifierClient as apiClient } from '../graphql/client/notifier'
|
import { notifierClient as apiClient } from '../graphql/client/notifier'
|
||||||
import { Notification } from '../graphql/schema/notifier.gen'
|
import { Notification } from '../graphql/schema/notifier.gen'
|
||||||
|
|
||||||
import { SSEMessage, useConnect } from './connect'
|
import { SSEMessage, useConnect } from './connect'
|
||||||
|
|
||||||
type NotificationsContextType = {
|
type NotificationsContextType = {
|
||||||
|
@ -70,7 +71,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
const markNotificationAsRead = async (notification: Notification) => {
|
const markNotificationAsRead = async (notification: Notification) => {
|
||||||
await apiClient.markNotificationAsRead(notification.id)
|
await apiClient.markNotificationAsRead(notification.id)
|
||||||
const nnn = new Set([...notification.seen, notification.id])
|
const nnn = new Set([...notification.seen, notification.id])
|
||||||
setNotificationEntities(notification.id, 'seen', Array.from(nnn))
|
setNotificationEntities(notification.id, 'seen', [...nnn])
|
||||||
setUnreadNotificationsCount((oldCount) => oldCount - 1)
|
setUnreadNotificationsCount((oldCount) => oldCount - 1)
|
||||||
}
|
}
|
||||||
const markAllNotificationsAsRead = async () => {
|
const markAllNotificationsAsRead = async () => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import type { AuthModalSource } from '../components/Nav/AuthModal/types'
|
||||||
import type { Author, Result } from '../graphql/schema/core.gen'
|
import type { Author, Result } from '../graphql/schema/core.gen'
|
||||||
import type { Accessor, JSX, Resource } from 'solid-js'
|
import type { Accessor, JSX, Resource } from 'solid-js'
|
||||||
|
|
||||||
|
import { VerifyEmailInput, LoginInput, AuthToken, User } from '@authorizerdev/authorizer-js'
|
||||||
import {
|
import {
|
||||||
createEffect,
|
createEffect,
|
||||||
createContext,
|
createContext,
|
||||||
|
@ -16,10 +17,9 @@ import { apiClient } from '../graphql/client/core'
|
||||||
import { getToken, resetToken, setToken } from '../graphql/privateGraphQLClient'
|
import { getToken, resetToken, setToken } from '../graphql/privateGraphQLClient'
|
||||||
import { showModal } from '../stores/ui'
|
import { showModal } from '../stores/ui'
|
||||||
|
|
||||||
|
import { useAuthorizer } from './authorizer'
|
||||||
import { useLocalize } from './localize'
|
import { useLocalize } from './localize'
|
||||||
import { useSnackbar } from './snackbar'
|
import { useSnackbar } from './snackbar'
|
||||||
import { useAuthorizer } from './authorizer'
|
|
||||||
import { VerifyEmailInput, LoginInput, AuthToken, User } from '@authorizerdev/authorizer-js'
|
|
||||||
|
|
||||||
export type SessionContextType = {
|
export type SessionContextType = {
|
||||||
session: Resource<AuthToken>
|
session: Resource<AuthToken>
|
||||||
|
@ -77,13 +77,13 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
const authResult = await authorizer().getSession({
|
const authResult = await authorizer().getSession({
|
||||||
Authorization: token,
|
Authorization: token,
|
||||||
})
|
})
|
||||||
if (!authResult) {
|
if (authResult) {
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
console.log(authResult)
|
console.log(authResult)
|
||||||
setToken(authResult.access_token || authResult.id_token)
|
setToken(authResult.access_token || authResult.id_token)
|
||||||
loadSubscriptions()
|
loadSubscriptions()
|
||||||
return authResult
|
return authResult
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('getSession error:', error)
|
console.error('getSession error:', error)
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
import { ApiError } from '../error'
|
|
||||||
import authConfirmEmailMutation from '../mutation/_auth/auth-confirm-email'
|
|
||||||
import authLogoutQuery from '../mutation/_auth/auth-logout'
|
|
||||||
import authRegisterMutation from '../mutation/_auth/auth-register'
|
|
||||||
import authSendLinkMutation from '../mutation/_auth/auth-send-link'
|
|
||||||
import mySession from '../mutation/_auth/my-session'
|
|
||||||
import { getToken, getPrivateClient } from '../privateGraphQLClient'
|
|
||||||
import { getPublicClient } from '../publicGraphQLClient'
|
|
||||||
import authCheckEmailQuery from '../query/_auth/auth-check-email'
|
|
||||||
import authLoginQuery from '../query/_auth/auth-login'
|
|
||||||
import {
|
|
||||||
ResendVerifyEmailInput,
|
|
||||||
ResetPasswordInput,
|
|
||||||
CreateUserInput,
|
|
||||||
DeleteUserInput,
|
|
||||||
UpdateUserInput,
|
|
||||||
LoginInput,
|
|
||||||
SignUpInput,
|
|
||||||
MagicLinkLoginInput,
|
|
||||||
AuthResponse,
|
|
||||||
} from '../schema/auth.gen'
|
|
||||||
|
|
||||||
export const authPublicGraphQLClient = getPublicClient('auth')
|
|
||||||
export const authPrivateGraphqlClient = getPrivateClient('auth')
|
|
||||||
|
|
||||||
export const authApiClient = {
|
|
||||||
authLogin: async ({ email, password }: { email: string; password: string }): Promise<AuthResponse> => {
|
|
||||||
const response = await authPublicGraphQLClient.query(authLoginQuery, { email, password }).toPromise()
|
|
||||||
// console.debug('[api-client] authLogin', { response })
|
|
||||||
if (response.error) {
|
|
||||||
if (
|
|
||||||
response.error.message === '[GraphQL] User not found' ||
|
|
||||||
response.error.message === "[GraphQL] 'dict' object has no attribute 'id'"
|
|
||||||
) {
|
|
||||||
throw new ApiError('user_not_found')
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ApiError('unknown', response.error.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.data.signIn.error) {
|
|
||||||
if (response.data.signIn.error === 'please, confirm email') {
|
|
||||||
throw new ApiError('email_not_confirmed')
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ApiError('unknown', response.data.signIn.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data.signIn
|
|
||||||
},
|
|
||||||
authRegister: async ({
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
name,
|
|
||||||
}: {
|
|
||||||
email: string
|
|
||||||
password: string
|
|
||||||
name: string
|
|
||||||
}): Promise<void> => {
|
|
||||||
const response = await authPublicGraphQLClient
|
|
||||||
.mutation(authRegisterMutation, { email, password, name })
|
|
||||||
.toPromise()
|
|
||||||
|
|
||||||
if (response.error) {
|
|
||||||
if (response.error.message === '[GraphQL] User already exist') {
|
|
||||||
throw new ApiError('user_already_exists', response.error.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ApiError('unknown', response.error.message)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
authSignOut: async () => {
|
|
||||||
const response = await authPublicGraphQLClient.query(authLogoutQuery, {}).toPromise()
|
|
||||||
return response.data.signOut
|
|
||||||
},
|
|
||||||
authCheckEmail: async ({ email }) => {
|
|
||||||
// check if email is used
|
|
||||||
const response = await authPublicGraphQLClient.query(authCheckEmailQuery, { email }).toPromise()
|
|
||||||
return response.data.isEmailUsed
|
|
||||||
},
|
|
||||||
authSendLink: async ({ email, lang, template }) => {
|
|
||||||
// send link with code on email
|
|
||||||
const response = await authPublicGraphQLClient
|
|
||||||
.mutation(authSendLinkMutation, { email, lang, template })
|
|
||||||
.toPromise()
|
|
||||||
|
|
||||||
if (response.error) {
|
|
||||||
if (response.error.message === '[GraphQL] User not found') {
|
|
||||||
throw new ApiError('user_not_found', response.error.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ApiError('unknown', response.error.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.data.sendLink.error) {
|
|
||||||
throw new ApiError('unknown', response.data.sendLink.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data.sendLink
|
|
||||||
},
|
|
||||||
confirmEmail: async ({ token }: { token: string }) => {
|
|
||||||
// confirm email with code from link
|
|
||||||
const response = await authPublicGraphQLClient.mutation(authConfirmEmailMutation, { token }).toPromise()
|
|
||||||
if (response.error) {
|
|
||||||
// TODO: better error communication
|
|
||||||
if (response.error.message === '[GraphQL] check token lifetime') {
|
|
||||||
throw new ApiError('token_expired', response.error.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.error.message === '[GraphQL] token is not valid') {
|
|
||||||
throw new ApiError('token_invalid', response.error.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ApiError('unknown', response.error.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.data?.confirmEmail?.error) {
|
|
||||||
throw new ApiError('unknown', response.data?.confirmEmail?.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data.confirmEmail
|
|
||||||
},
|
|
||||||
getSession: async (): Promise<AuthResponse> => {
|
|
||||||
if (!getToken()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// renew session with auth token in header (!)
|
|
||||||
const response = await authPrivateGraphqlClient.mutation(mySession, {}).toPromise()
|
|
||||||
|
|
||||||
if (response.error) {
|
|
||||||
throw new ApiError('unknown', response.error.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.data?.getSession?.error) {
|
|
||||||
throw new ApiError('unknown', response.data.getSession.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data.getSession
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -11,67 +11,67 @@ import chatMessagesLoadBy from '../query/chat/chat-messages-load-by'
|
||||||
import loadRecipients from '../query/chat/chat-recipients'
|
import loadRecipients from '../query/chat/chat-recipients'
|
||||||
import myChats from '../query/chat/chats-load'
|
import myChats from '../query/chat/chats-load'
|
||||||
import {
|
import {
|
||||||
QueryLoadChatsArgs,
|
|
||||||
QueryLoadMessagesByArgs,
|
|
||||||
MutationCreateChatArgs,
|
|
||||||
MutationCreateMessageArgs,
|
|
||||||
QueryLoadRecipientsArgs,
|
|
||||||
Chat,
|
Chat,
|
||||||
MutationMarkAsReadArgs,
|
MutationCreate_ChatArgs,
|
||||||
MutationDeleteChatArgs,
|
MutationCreate_MessageArgs,
|
||||||
MutationUpdateChatArgs,
|
MutationDelete_ChatArgs,
|
||||||
MutationUpdateMessageArgs,
|
MutationDelete_MessageArgs,
|
||||||
MutationDeleteMessageArgs,
|
MutationMark_As_ReadArgs,
|
||||||
|
MutationUpdate_ChatArgs,
|
||||||
|
MutationUpdate_MessageArgs,
|
||||||
|
QueryLoad_ChatsArgs,
|
||||||
|
QueryLoad_Messages_ByArgs,
|
||||||
|
QueryLoad_RecipientsArgs,
|
||||||
} from '../schema/chat.gen'
|
} from '../schema/chat.gen'
|
||||||
|
|
||||||
const privateInboxGraphQLClient = getPrivateClient('chat')
|
const privateInboxGraphQLClient = getPrivateClient('chat')
|
||||||
|
|
||||||
export const inboxClient = {
|
export const inboxClient = {
|
||||||
loadChats: async (options: QueryLoadChatsArgs): Promise<Chat[]> => {
|
loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => {
|
||||||
const resp = await privateInboxGraphQLClient.query(myChats, options).toPromise()
|
const resp = await privateInboxGraphQLClient.query(myChats, options).toPromise()
|
||||||
return resp.data.load_chats.chats
|
return resp.data.load_chats.chats
|
||||||
},
|
},
|
||||||
|
|
||||||
createChat: async (options: MutationCreateChatArgs) => {
|
createChat: async (options: MutationCreate_ChatArgs) => {
|
||||||
const resp = await privateInboxGraphQLClient.mutation(createChat, options).toPromise()
|
const resp = await privateInboxGraphQLClient.mutation(createChat, options).toPromise()
|
||||||
return resp.data.create_chat
|
return resp.data.create_chat
|
||||||
},
|
},
|
||||||
|
|
||||||
markAsRead: async (options: MutationMarkAsReadArgs) => {
|
markAsRead: async (options: MutationMark_As_ReadArgs) => {
|
||||||
const resp = await privateInboxGraphQLClient.mutation(markAsRead, options).toPromise()
|
const resp = await privateInboxGraphQLClient.mutation(markAsRead, options).toPromise()
|
||||||
return resp.data.mark_as_read
|
return resp.data.mark_as_read
|
||||||
},
|
},
|
||||||
|
|
||||||
updateChat: async (options: MutationUpdateChatArgs) => {
|
updateChat: async (options: MutationUpdate_ChatArgs) => {
|
||||||
const resp = await privateInboxGraphQLClient.mutation(updateChat, options).toPromise()
|
const resp = await privateInboxGraphQLClient.mutation(updateChat, options).toPromise()
|
||||||
return resp.data.update_chat
|
return resp.data.update_chat
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteChat: async (options: MutationDeleteChatArgs) => {
|
deleteChat: async (options: MutationDelete_ChatArgs) => {
|
||||||
const resp = await privateInboxGraphQLClient.mutation(deleteChat, options).toPromise()
|
const resp = await privateInboxGraphQLClient.mutation(deleteChat, options).toPromise()
|
||||||
return resp.data.delete_chat
|
return resp.data.delete_chat
|
||||||
},
|
},
|
||||||
|
|
||||||
createMessage: async (options: MutationCreateMessageArgs) => {
|
createMessage: async (options: MutationCreate_MessageArgs) => {
|
||||||
const resp = await privateInboxGraphQLClient.mutation(createChatMessage, options).toPromise()
|
const resp = await privateInboxGraphQLClient.mutation(createChatMessage, options).toPromise()
|
||||||
return resp.data.create_message.message
|
return resp.data.create_message.message
|
||||||
},
|
},
|
||||||
|
|
||||||
updateMessage: async (options: MutationUpdateMessageArgs) => {
|
updateMessage: async (options: MutationUpdate_MessageArgs) => {
|
||||||
const resp = await privateInboxGraphQLClient.mutation(updateChatMessage, options).toPromise()
|
const resp = await privateInboxGraphQLClient.mutation(updateChatMessage, options).toPromise()
|
||||||
return resp.data.update_message.message
|
return resp.data.update_message.message
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteMessage: async (options: MutationDeleteMessageArgs) => {
|
deleteMessage: async (options: MutationDelete_MessageArgs) => {
|
||||||
const resp = await privateInboxGraphQLClient.mutation(deleteChatMessage, options).toPromise()
|
const resp = await privateInboxGraphQLClient.mutation(deleteChatMessage, options).toPromise()
|
||||||
return resp.data.delete_message
|
return resp.data.delete_message
|
||||||
},
|
},
|
||||||
|
|
||||||
loadChatMessages: async (options: QueryLoadMessagesByArgs) => {
|
loadChatMessages: async (options: QueryLoad_Messages_ByArgs) => {
|
||||||
const resp = await privateInboxGraphQLClient.query(chatMessagesLoadBy, options).toPromise()
|
const resp = await privateInboxGraphQLClient.query(chatMessagesLoadBy, options).toPromise()
|
||||||
return resp.data.load_messages_by.messages
|
return resp.data.load_messages_by.messages
|
||||||
},
|
},
|
||||||
loadRecipients: async (options: QueryLoadRecipientsArgs) => {
|
loadRecipients: async (options: QueryLoad_RecipientsArgs) => {
|
||||||
const resp = await privateInboxGraphQLClient.query(loadRecipients, options).toPromise()
|
const resp = await privateInboxGraphQLClient.query(loadRecipients, options).toPromise()
|
||||||
return resp.data.load_recipients.members
|
return resp.data.load_recipients.members
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,12 +4,12 @@ import type {
|
||||||
Topic,
|
Topic,
|
||||||
Author,
|
Author,
|
||||||
LoadShoutsOptions,
|
LoadShoutsOptions,
|
||||||
QueryLoadAuthorsByArgs,
|
|
||||||
ProfileInput,
|
ProfileInput,
|
||||||
ReactionInput,
|
ReactionInput,
|
||||||
ReactionBy,
|
ReactionBy,
|
||||||
Shout,
|
Shout,
|
||||||
Result,
|
Result,
|
||||||
|
QueryLoad_Authors_ByArgs,
|
||||||
} from '../schema/core.gen'
|
} from '../schema/core.gen'
|
||||||
|
|
||||||
import createArticle from '../mutation/core/article-create'
|
import createArticle from '../mutation/core/article-create'
|
||||||
|
@ -25,18 +25,18 @@ import { getPrivateClient } from '../privateGraphQLClient'
|
||||||
import { getPublicClient } from '../publicGraphQLClient'
|
import { getPublicClient } from '../publicGraphQLClient'
|
||||||
import shoutLoad from '../query/core/article-load'
|
import shoutLoad from '../query/core/article-load'
|
||||||
import shoutsLoadBy from '../query/core/articles-load-by'
|
import shoutsLoadBy from '../query/core/articles-load-by'
|
||||||
import authorBy from '../query/core/author-by'
|
|
||||||
import authorFollowers from '../query/core/author-followers'
|
|
||||||
import authorFollowed from '../query/core/authors-followed-by'
|
|
||||||
import userFollowedTopics from '../query/core/topics-by-author'
|
|
||||||
import authorsAll from '../query/core/authors-all'
|
|
||||||
import authorsLoadBy from '../query/core/authors-load-by'
|
|
||||||
import draftsLoad from '../query/core/articles-load-drafts'
|
import draftsLoad from '../query/core/articles-load-drafts'
|
||||||
import myFeed from '../query/core/articles-load-feed'
|
import myFeed from '../query/core/articles-load-feed'
|
||||||
|
import authorBy from '../query/core/author-by'
|
||||||
|
import authorFollowers from '../query/core/author-followers'
|
||||||
|
import authorsAll from '../query/core/authors-all'
|
||||||
|
import authorFollowed from '../query/core/authors-followed-by'
|
||||||
|
import authorsLoadBy from '../query/core/authors-load-by'
|
||||||
import mySubscriptions from '../query/core/my-followed'
|
import mySubscriptions from '../query/core/my-followed'
|
||||||
import reactionsLoadBy from '../query/core/reactions-load-by'
|
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 userFollowedTopics from '../query/core/topics-by-author'
|
||||||
import topicsRandomQuery from '../query/core/topics-random'
|
import topicsRandomQuery from '../query/core/topics-random'
|
||||||
|
|
||||||
export const privateGraphQLClient = getPublicClient('core')
|
export const privateGraphQLClient = getPublicClient('core')
|
||||||
|
@ -145,7 +145,7 @@ export const apiClient = {
|
||||||
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.reaction
|
||||||
},
|
},
|
||||||
getAuthorsBy: async (options: QueryLoadAuthorsByArgs) => {
|
getAuthorsBy: async (options: QueryLoad_Authors_ByArgs) => {
|
||||||
const resp = await publicGraphQLClient.query(authorsLoadBy, options).toPromise()
|
const resp = await publicGraphQLClient.query(authorsLoadBy, options).toPromise()
|
||||||
return resp.data.load_authors_by
|
return resp.data.load_authors_by
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@ export const notifierPrivateGraphqlClient = getPrivateClient('notifier')
|
||||||
|
|
||||||
export const notifierClient = {
|
export const notifierClient = {
|
||||||
getNotifications: async (params: QueryLoad_NotificationsArgs): Promise<NotificationsResult> => {
|
getNotifications: async (params: QueryLoad_NotificationsArgs): Promise<NotificationsResult> => {
|
||||||
const resp = await notifierPrivateGraphqlClient.query(loadNotifications, { params }).toPromise()
|
const resp = await notifierPrivateGraphqlClient.query(loadNotifications, params).toPromise()
|
||||||
return resp.data.load_notifications
|
return resp.data.load_notifications
|
||||||
},
|
},
|
||||||
markNotificationAsRead: async (notification_id: number): Promise<void> => {
|
markNotificationAsRead: async (notification_id: number): Promise<void> => {
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
mutation ConfirmEmailMutation($token: String!) {
|
|
||||||
confirmEmail(token: $token) {
|
|
||||||
error
|
|
||||||
token
|
|
||||||
user {
|
|
||||||
id
|
|
||||||
email
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
bio
|
|
||||||
userpic
|
|
||||||
links
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
query SignOutQuery {
|
|
||||||
signOut {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
mutation RegisterMutation($email: String!, $password: String!, $name: String!) {
|
|
||||||
registerUser(email: $email, password: $password, name: $name) {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
mutation SendLinkQuery($email: String!, $lang: String, $template: String) {
|
|
||||||
sendLink(email: $email, lang: $lang, template: $template) {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
mutation GetSessionMutation {
|
|
||||||
getSession {
|
|
||||||
error
|
|
||||||
token
|
|
||||||
user {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
bio
|
|
||||||
userpic
|
|
||||||
links
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -41,7 +41,7 @@ const options: ClientOptions = {
|
||||||
}
|
}
|
||||||
const headers = { Authorization: token }
|
const headers = { Authorization: token }
|
||||||
return { headers }
|
return { headers }
|
||||||
} catch (_) {
|
} catch {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -51,5 +51,5 @@ const options: ClientOptions = {
|
||||||
export const getPrivateClient = (name: string) =>
|
export const getPrivateClient = (name: string) =>
|
||||||
createClient({
|
createClient({
|
||||||
...options,
|
...options,
|
||||||
url: `https://${name.replace('core', 'testapi')}.discours.io`,
|
url: `https://${name}.discours.io`,
|
||||||
})
|
})
|
||||||
|
|
|
@ -20,5 +20,5 @@ const options: ClientOptions = {
|
||||||
export const getPublicClient = (name: string) =>
|
export const getPublicClient = (name: string) =>
|
||||||
createClient({
|
createClient({
|
||||||
...options,
|
...options,
|
||||||
url: `https://${name.replace('core', 'testapi')}.discours.io`,
|
url: `https://${name}.discours.io`,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
query isEmailUsedQuery($email: String!) {
|
|
||||||
isEmailUsed(email: $email)
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
query SignInQuery($email: String!, $password: String!) {
|
|
||||||
signIn(email: $email, password: $password) {
|
|
||||||
error
|
|
||||||
token
|
|
||||||
user {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
userpic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
query GetUserRolesBySlug($slug: String!) {
|
|
||||||
getUserRoles(slug: $slug) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
# community
|
|
||||||
desc
|
|
||||||
permissions {
|
|
||||||
operation_id
|
|
||||||
resource_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,4 +1,7 @@
|
||||||
import { authApiClient as apiClient } from '../graphql/client/_auth'
|
import { MagicLinkLoginInput, SignupInput } from '@authorizerdev/authorizer-js'
|
||||||
|
import { useAuthorizer } from '../context/authorizer'
|
||||||
|
|
||||||
|
const [, { authorizer }] = useAuthorizer()
|
||||||
|
|
||||||
export const register = async ({
|
export const register = async ({
|
||||||
name,
|
name,
|
||||||
|
@ -9,11 +12,12 @@ export const register = async ({
|
||||||
email: string
|
email: string
|
||||||
password: string
|
password: string
|
||||||
}) => {
|
}) => {
|
||||||
await apiClient.authRegister({
|
const signupInput: SignupInput = {
|
||||||
name,
|
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
})
|
confirm_password: password,
|
||||||
|
}
|
||||||
|
await authorizer().signup(signupInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const signSendLink = async ({
|
export const signSendLink = async ({
|
||||||
|
@ -25,5 +29,5 @@ export const signSendLink = async ({
|
||||||
lang: string
|
lang: string
|
||||||
template: string
|
template: string
|
||||||
}) => {
|
}) => {
|
||||||
return await apiClient.authSendLink({ email, lang, template })
|
return await authorizer().magicLinkLogin({ email } as MagicLinkLoginInput)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
import { createSignal } from 'solid-js'
|
import { createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { authApiClient as apiClient } from '../graphql/client/_auth'
|
|
||||||
|
|
||||||
const [emailChecks, setEmailChecks] = createSignal<{ [email: string]: boolean }>({})
|
const [emailChecks, setEmailChecks] = createSignal<{ [email: string]: boolean }>({})
|
||||||
|
|
||||||
export const checkEmail = async (email: string): Promise<boolean> => {
|
export const checkEmail = async (email: string): Promise<boolean> => {
|
||||||
if (emailChecks()[email]) {
|
if (emailChecks()[email]) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
const checkResult = false // FIXME: secure endpoint for check email
|
||||||
const checkResult = await apiClient.authCheckEmail({ email })
|
|
||||||
|
|
||||||
if (checkResult) {
|
if (checkResult) {
|
||||||
setEmailChecks((oldEmailChecks) => ({ ...oldEmailChecks, [email]: true }))
|
setEmailChecks((oldEmailChecks) => ({ ...oldEmailChecks, [email]: true }))
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import type { Author } from '../../graphql/schema/core.gen'
|
|
||||||
|
|
||||||
import { createLazyMemo } from '@solid-primitives/memo'
|
import { createLazyMemo } from '@solid-primitives/memo'
|
||||||
import { createSignal } from 'solid-js'
|
import { createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { apiClient } from '../../graphql/client/core'
|
import { apiClient } from '../../graphql/client/core'
|
||||||
import { byStat } from '../../utils/sortby'
|
import { byStat } from '../../utils/sortby'
|
||||||
|
import { Author } from '../../graphql/schema/core.gen'
|
||||||
|
|
||||||
export type AuthorsSortBy = 'shouts' | 'name' | 'followers'
|
export type AuthorsSortBy = 'shouts' | 'name' | 'followers'
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import type { FollowingEntity } from '../../graphql/schema/core.gen'
|
|
||||||
|
|
||||||
import { apiClient } from '../../graphql/client/core'
|
import { apiClient } from '../../graphql/client/core'
|
||||||
|
import { FollowingEntity } from '../../graphql/schema/core.gen'
|
||||||
|
|
||||||
export const follow = async ({ what, slug }: { what: FollowingEntity; slug: string }) => {
|
export const follow = async ({ what, slug }: { what: FollowingEntity; slug: string }) => {
|
||||||
await apiClient.follow({ what, slug })
|
await apiClient.follow({ what, slug })
|
||||||
|
|
|
@ -8,13 +8,16 @@ export type LayoutType = 'article' | 'audio' | 'video' | 'image' | 'literature'
|
||||||
|
|
||||||
const [sortedLayoutShouts, setSortedLayoutShouts] = createSignal<Map<LayoutType, Shout[]>>(new Map())
|
const [sortedLayoutShouts, setSortedLayoutShouts] = createSignal<Map<LayoutType, Shout[]>>(new Map())
|
||||||
|
|
||||||
const addLayoutShouts = (layout: LayoutType, shouts: Shout[]) => {
|
const addLayoutShouts = (layouts: LayoutType[], shouts: Shout[]) => {
|
||||||
setSortedLayoutShouts((prevSorted: Map<LayoutType, Shout[]>) => {
|
setSortedLayoutShouts((prevSorted: Map<LayoutType, Shout[]>) => {
|
||||||
const siblings = prevSorted.get(layout)
|
layouts.forEach((layout: LayoutType) => {
|
||||||
if (siblings) {
|
const siblings = prevSorted.get(layout)
|
||||||
const uniqued = [...new Set([...siblings, ...shouts])]
|
if (siblings) {
|
||||||
prevSorted.set(layout, uniqued)
|
const uniqued = [...new Set([...siblings, ...shouts])]
|
||||||
}
|
prevSorted.set(layout, uniqued)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return prevSorted
|
return prevSorted
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -34,13 +37,13 @@ export const loadLayoutShoutsBy = async (options: LoadShoutsOptions): Promise<{
|
||||||
if (hasMore) {
|
if (hasMore) {
|
||||||
newLayoutShouts.splice(-1)
|
newLayoutShouts.splice(-1)
|
||||||
}
|
}
|
||||||
addLayoutShouts(options.filters.layout as LayoutType, newLayoutShouts)
|
addLayoutShouts(options.filters.layouts as LayoutType[], newLayoutShouts)
|
||||||
|
|
||||||
return { hasMore }
|
return { hasMore }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useLayoutsStore = (layout: LayoutType, initialData: Shout[]) => {
|
export const useLayoutsStore = (layouts: LayoutType[], initialData: Shout[]) => {
|
||||||
addLayoutShouts(layout, initialData || [])
|
addLayoutShouts(layouts, initialData || [])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addLayoutShouts,
|
addLayoutShouts,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user