auth-minor-fixes

This commit is contained in:
Untone 2024-05-24 17:59:15 +03:00
parent b53aa85337
commit 9a42086f08
13 changed files with 128 additions and 114 deletions

View File

@ -1,21 +1,8 @@
{ {
"$schema": "https://biomejs.dev/schemas/1.7.2/schema.json", "$schema": "https://biomejs.dev/schemas/1.7.2/schema.json",
"files": { "files": {
"include": [ "include": ["*.tsx", "*.ts", "*.js", "*.json"],
"*.tsx", "ignore": ["./dist", "./node_modules", ".husky", "docs", "gen", "*.gen.ts", "*.d.ts"]
"*.ts",
"*.js",
"*.json"
],
"ignore": [
"./dist",
"./node_modules",
".husky",
"docs",
"gen",
"*.gen.ts",
"*.d.ts"
]
}, },
"vcs": { "vcs": {
"defaultBranch": "dev", "defaultBranch": "dev",
@ -23,19 +10,13 @@
}, },
"organizeImports": { "organizeImports": {
"enabled": true, "enabled": true,
"ignore": [ "ignore": ["./api", "./gen"]
"./api",
"./gen"
]
}, },
"formatter": { "formatter": {
"indentStyle": "space", "indentStyle": "space",
"indentWidth": 2, "indentWidth": 2,
"lineWidth": 108, "lineWidth": 108,
"ignore": [ "ignore": ["./src/graphql/schema", "./gen"]
"./src/graphql/schema",
"./gen"
]
}, },
"javascript": { "javascript": {
"formatter": { "formatter": {
@ -48,13 +29,7 @@
} }
}, },
"linter": { "linter": {
"ignore": [ "ignore": ["*.scss", "*.md", ".DS_Store", "*.svg", "*.d.ts"],
"*.scss",
"*.md",
".DS_Store",
"*.svg",
"*.d.ts"
],
"enabled": true, "enabled": true,
"rules": { "rules": {
"all": true, "all": true,

View File

@ -95,7 +95,7 @@
"fast-deep-equal": "3.1.3", "fast-deep-equal": "3.1.3",
"ga-gtag": "1.2.0", "ga-gtag": "1.2.0",
"graphql": "16.8.1", "graphql": "16.8.1",
"graphql-tag": "2.12.6", "graphql-tag": "^2.12.6",
"i18next": "22.4.15", "i18next": "22.4.15",
"i18next-http-backend": "2.2.0", "i18next-http-backend": "2.2.0",
"i18next-icu": "2.3.0", "i18next-icu": "2.3.0",
@ -137,5 +137,7 @@
"y-prosemirror": "1.2.5", "y-prosemirror": "1.2.5",
"yjs": "13.6.15" "yjs": "13.6.15"
}, },
"trustedDependencies": ["@biomejs/biome"] "trustedDependencies": [
"@biomejs/biome"
]
} }

View File

@ -197,8 +197,10 @@ export const AuthorCard = (props: Props) => {
<Show when={props.followers && props.followers.length > 0}> <Show when={props.followers && props.followers.length > 0}>
<a href="?m=followers" class={styles.followers}> <a href="?m=followers" class={styles.followers}>
<For each={props.followers.slice(0, 3)}> <For each={props.followers.slice(0, 3)}>
{(f) => ( {(f: Author) => (
<Userpic size={'XS'} name={f.name} userpic={f.pic} class={styles.followersItem} /> <Show when={f?.name}>
<Userpic size={'XS'} name={f?.name || ''} userpic={f?.pic || ''} class={styles.followersItem} />
</Show>
)} )}
</For> </For>
<div class={styles.followsCounter}> <div class={styles.followsCounter}>

View File

@ -15,20 +15,24 @@ import styles from './DraftsView.module.scss'
export const DraftsView = () => { export const DraftsView = () => {
const { author, loadSession } = useSession() const { author, loadSession } = useSession()
const [drafts, setDrafts] = createSignal<Shout[]>([]) const [drafts, setDrafts] = createSignal<Shout[]>([])
const [loading, setLoading] = createSignal(false)
createEffect( createEffect(
on( on(
() => author(), () => author(),
async (a) => { async (a) => {
if (a) { if (a) {
setLoading(true)
const { shouts: loadedDrafts, error } = await apiClient.getDrafts() const { shouts: loadedDrafts, error } = await apiClient.getDrafts()
if (error) { if (error) {
console.warn(error) console.warn(error)
await loadSession() await loadSession()
} }
setDrafts(loadedDrafts || []) setDrafts(loadedDrafts || [])
setLoading(false)
} }
}, },
{ defer: true },
), ),
) )
@ -50,7 +54,7 @@ export const DraftsView = () => {
return ( return (
<div class={clsx(styles.DraftsView)}> <div class={clsx(styles.DraftsView)}>
<Show when={author()?.id} fallback={<Loading />}> <Show when={!loading() && author()?.id} fallback={<Loading />}>
<div class="wide-container"> <div class="wide-container">
<div class="row"> <div class="row">
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5"> <div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">

View File

@ -28,6 +28,7 @@ import { EditorSwiper } from '../../_shared/SolidSwiper'
import { PublishSettings } from '../PublishSettings' import { PublishSettings } from '../PublishSettings'
import styles from './EditView.module.scss' import styles from './EditView.module.scss'
import { Loading } from '../../_shared/Loading'
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
const GrowingTextarea = lazy(() => import('../../_shared/GrowingTextarea/GrowingTextarea')) const GrowingTextarea = lazy(() => import('../../_shared/GrowingTextarea/GrowingTextarea'))
@ -403,7 +404,7 @@ export const EditView = (props: Props) => {
</Show> </Show>
</div> </div>
</div> </div>
<Show when={page().route === 'edit'}> <Show when={page().route === 'edit' && form?.shoutId} fallback={<Loading />}>
<Editor <Editor
shoutId={form.shoutId} shoutId={form.shoutId}
initialContent={form.body} initialContent={form.body}

View File

@ -40,7 +40,18 @@ const EMPTY_TOPIC: Topic = {
id: -1, id: -1,
slug: '', slug: '',
} }
const emptyConfig = {
interface FormConfig {
coverImageUrl?: string
mainTopic?: Topic
slug?: string
title?: string
subtitle?: string
description?: string
selectedTopics?: Topic[]
}
const emptyConfig: FormConfig = {
coverImageUrl: '', coverImageUrl: '',
mainTopic: EMPTY_TOPIC, mainTopic: EMPTY_TOPIC,
slug: '', slug: '',
@ -78,7 +89,7 @@ export const PublishSettings = (props: Props) => {
} }
}) })
const [settingsForm, setSettingsForm] = createStore(emptyConfig) const [settingsForm, setSettingsForm] = createStore<FormConfig>(emptyConfig)
onMount(() => { onMount(() => {
setSettingsForm(initialData()) setSettingsForm(initialData())
@ -96,12 +107,12 @@ export const PublishSettings = (props: Props) => {
setSettingsForm('coverImageUrl', '') setSettingsForm('coverImageUrl', '')
} }
const handleTopicSelectChange = (newSelectedTopics) => { const handleTopicSelectChange = (newSelectedTopics: Topic[]) => {
if ( if (
props.form.selectedTopics.length === 0 || props.form.selectedTopics.length === 0 ||
newSelectedTopics.every((topic) => topic.id !== props.form.mainTopic?.id) newSelectedTopics.every((topic: Topic) => topic.id !== props.form.mainTopic?.id)
) { ) {
setSettingsForm((prev) => { setSettingsForm((prev: Topic) => {
return { return {
...prev, ...prev,
mainTopic: newSelectedTopics[0], mainTopic: newSelectedTopics[0],
@ -193,7 +204,8 @@ export const PublishSettings = (props: Props) => {
fieldName={t('Header')} fieldName={t('Header')}
placeholder={t('Come up with a title for your story')} placeholder={t('Come up with a title for your story')}
initialValue={settingsForm.title} initialValue={settingsForm.title}
value={(value) => setSettingsForm('title', value)} // biome-ignore lint/suspicious/noExplicitAny: <explanation>
value={(value: any) => setSettingsForm('title', value)}
allowEnterKey={false} allowEnterKey={false}
maxLength={100} maxLength={100}
/> />
@ -203,7 +215,8 @@ export const PublishSettings = (props: Props) => {
fieldName={t('Subheader')} fieldName={t('Subheader')}
placeholder={t('Come up with a subtitle for your story')} placeholder={t('Come up with a subtitle for your story')}
initialValue={settingsForm.subtitle || ''} initialValue={settingsForm.subtitle || ''}
value={(value) => setSettingsForm('subtitle', value)} // biome-ignore lint/suspicious/noExplicitAny: <explanation>
value={(value: any) => setSettingsForm('subtitle', value)}
allowEnterKey={false} allowEnterKey={false}
maxLength={100} maxLength={100}
/> />
@ -214,7 +227,8 @@ export const PublishSettings = (props: Props) => {
placeholder={t('Write a short introduction')} placeholder={t('Write a short introduction')}
label={t('Description')} label={t('Description')}
initialContent={composeDescription()} initialContent={composeDescription()}
onChange={(value) => setForm('description', value)} // biome-ignore lint/suspicious/noExplicitAny: <explanation>
onChange={(value: any) => setForm('description', value)}
maxLength={DESCRIPTION_MAX_LENGTH} maxLength={DESCRIPTION_MAX_LENGTH}
/> />
</div> </div>

View File

@ -42,7 +42,7 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
createEffect( createEffect(
on( on(
() => session()?.access_token, () => session()?.access_token,
async ([tkn]) => { async (tkn) => {
if (!sseUrl) return if (!sseUrl) return
if (!tkn) return if (!tkn) return
if (!connected() && retried() <= RECONNECT_TIMES) { if (!connected() && retried() <= RECONNECT_TIMES) {
@ -67,7 +67,7 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
return Promise.resolve() return Promise.resolve()
} }
return Promise.reject( return Promise.reject(
`SSE: cannot connect to real-time updates, status: ${response.status}`, `SSE: cannot connect to real-time updates: ${response.status}`,
) )
}, },
onclose() { onclose() {
@ -75,7 +75,7 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
setConnected(false) setConnected(false)
if (retried() < RECONNECT_TIMES) { if (retried() < RECONNECT_TIMES) {
setRetried((r) => r + 1) setRetried((r) => r + 1)
} } else throw Error('closed by server')
}, },
onerror(err) { onerror(err) {
console.error('[context.connect] SSE connection error:', err) console.error('[context.connect] SSE connection error:', err)

View File

@ -34,13 +34,14 @@ import { useRouter } from '../stores/router'
import { showModal } from '../stores/ui' import { showModal } from '../stores/ui'
import { addAuthors } from '../stores/zine/authors' import { addAuthors } from '../stores/zine/authors'
import { authApiUrl } from '../utils/config'
import { useLocalize } from './localize' import { useLocalize } from './localize'
import { useSnackbar } from './snackbar' import { useSnackbar } from './snackbar'
const defaultConfig: ConfigType = { const defaultConfig: ConfigType = {
authorizerURL: 'https://auth.discours.io', authorizerURL: authApiUrl.replace('/graphql', ''),
redirectURL: 'https://testing.discours.io', redirectURL: 'https://testing.discours.io',
clientID: 'b9038a34-ca59-41ae-a105-c7fbea603e24', // FIXME: use env? clientID: '',
} }
export type SessionContextType = { export type SessionContextType = {
@ -73,9 +74,32 @@ export type SessionContextType = {
resendVerifyEmail: (params: ResendVerifyEmailInput) => Promise<GenericResponse> resendVerifyEmail: (params: ResendVerifyEmailInput) => Promise<GenericResponse>
} }
// biome-ignore lint/suspicious/noEmptyBlockStatements: <explanation> const noop = () => null
const noop = () => {} const metaRes = {
data: {
meta: {
version: 'latest',
// client_id: 'b9038a34-ca59-41ae-a105-c7fbea603e24',
is_google_login_enabled: true,
is_facebook_login_enabled: true,
is_github_login_enabled: true,
is_linkedin_login_enabled: false,
is_apple_login_enabled: false,
is_twitter_login_enabled: true,
is_microsoft_login_enabled: false,
is_twitch_login_enabled: false,
is_roblox_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: false,
is_multi_factor_auth_enabled: true,
is_mobile_basic_authentication_enabled: true,
is_phone_verification_enabled: false,
},
},
}
const SessionContext = createContext<SessionContextType>() const SessionContext = createContext<SessionContextType>()
export function useSession() { export function useSession() {
@ -212,9 +236,12 @@ export const SessionProvider = (props: {
}) })
// when session is loaded // when session is loaded
createEffect(() => { createEffect(
if (session()) { on(
const token = session()?.access_token session,
(s: AuthToken) => {
if (s) {
const token = s?.access_token
if (token) { if (token) {
if (!inboxClient.private) { if (!inboxClient.private) {
apiClient.connect(token) apiClient.connect(token)
@ -235,21 +262,15 @@ export const SessionProvider = (props: {
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
setIsSessionLoaded(true) setIsSessionLoaded(true)
}
}
})
// when author is loaded
createEffect(() => {
if (author()) {
addAuthors([author()])
} else { } else {
reset() reset()
} }
}) }
},
{ defer: true },
),
)
const reset = () => { const reset = () => {
setIsSessionLoaded(true) setIsSessionLoaded(true)
setSession(null) setSession(null)
@ -257,20 +278,13 @@ export const SessionProvider = (props: {
} }
// initial effect // initial effect
onMount(async () => { onMount(() => {
const metaRes = await authorizer().getMetaData()
setConfig({ setConfig({
...defaultConfig, ...defaultConfig,
...metaRes, ...metaRes,
redirectURL: window.location.origin, redirectURL: window.location.origin,
}) })
let s: AuthToken loadSession()
try {
s = await loadSession()
} catch (error) {
console.warn('[context.session] load session failed', error)
}
if (!s) reset()
}) })
// callback state updater // callback state updater
@ -318,6 +332,7 @@ export const SessionProvider = (props: {
console.debug(authResult) console.debug(authResult)
reset() reset()
showSnackbar({ body: t("You've successfully logged out") }) showSnackbar({ body: t("You've successfully logged out") })
console.debug(session())
} }
const changePassword = async (password: string, token: string) => { const changePassword = async (password: string, token: string) => {

View File

@ -17,7 +17,7 @@ import styles from '../styles/Create.module.scss'
const handleCreate = async (layout: LayoutType) => { const handleCreate = async (layout: LayoutType) => {
const shout = await apiClient.createArticle({ article: { layout: layout } }) const shout = await apiClient.createArticle({ article: { layout: layout } })
redirectPage(router, 'edit', { shout?.id && redirectPage(router, 'edit', {
shoutId: shout?.id.toString(), shoutId: shout?.id.toString(),
}) })
} }

View File

@ -7,7 +7,7 @@ import { useLocalize } from '../context/localize'
import { useSession } from '../context/session' import { useSession } from '../context/session'
import { apiClient } from '../graphql/client/core' import { apiClient } from '../graphql/client/core'
import { Shout } from '../graphql/schema/core.gen' import { Shout } from '../graphql/schema/core.gen'
import { router, useRouter } from '../stores/router' import { router } from '../stores/router'
import { redirectPage } from '@nanostores/router' import { redirectPage } from '@nanostores/router'
import { useSnackbar } from '../context/snackbar' import { useSnackbar } from '../context/snackbar'
@ -33,7 +33,6 @@ const getContentTypeTitle = (layout: LayoutType) => {
export const EditPage = () => { export const EditPage = () => {
const { t } = useLocalize() const { t } = useLocalize()
const { session } = useSession() const { session } = useSession()
const { page } = useRouter()
const snackbar = useSnackbar() const snackbar = useSnackbar()
const fail = async (error: string) => { const fail = async (error: string) => {
@ -48,17 +47,19 @@ export const EditPage = () => {
createEffect( createEffect(
on( on(
() => page(), () => window?.location.pathname,
(p) => { (p) => {
if (p?.path) { if (p) {
console.debug(p?.path) console.debug(p)
const shoutId = p?.path.split('/').pop() const shoutId = p.split('/').pop()
if (shoutId) {
const shoutIdFromUrl = Number.parseInt(shoutId ?? '0', 10) const shoutIdFromUrl = Number.parseInt(shoutId ?? '0', 10)
console.debug(`editing shout ${shoutIdFromUrl}`) console.debug(`editing shout ${shoutIdFromUrl}`)
if (shoutIdFromUrl) { if (shoutIdFromUrl) {
setShoutId(shoutIdFromUrl) setShoutId(shoutIdFromUrl)
} }
} }
}
}, },
), ),
) )

View File

@ -2,7 +2,7 @@ export const isDev = import.meta.env.MODE === 'development'
export const cdnUrl = 'https://cdn.discours.io' export const cdnUrl = 'https://cdn.discours.io'
export const thumborUrl = import.meta.env.PUBLIC_THUMBOR_URL || 'https://images.discours.io' export const thumborUrl = import.meta.env.PUBLIC_THUMBOR_URL || 'https://images.discours.io'
export const errorsReportingDsn = import.meta.env.PUBLIC_GLITCHTIP_DSN || import.meta.env.PUBLIC_SENTRY_DSN || '' export const errorsReportingDsn = import.meta.env.PUBLIC_GLITCHTIP_DSN || import.meta.env.PUBLIC_SENTRY_DSN || ''
export const coreApiUrl = 'https://coretest.discours.io' export const coreApiUrl = import.meta.env.PUBLIC_API_BASE || 'https://coretest.discours.io'
export const chatApiUrl = 'https://inboxtest.discours.io' export const chatApiUrl = import.meta.env.PUBLIC_CHAT_API || 'https://inboxtest.discours.io'
export const authApiUrl = 'https://authtest.discours.io/graphql' export const authApiUrl = import.meta.env.PUBLIC_AUTH_API || 'https://authtest.discours.io/graphql'
export const sseUrl = 'https://presencetest.discours.io' export const sseUrl = import.meta.env.PUBLIC_REALTIME_EVENTS || 'https://presencetest.discours.io'