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

@ -541,4 +541,4 @@
"You've reached a non-existed page": "You've reached a non-existed page", "You've reached a non-existed page": "You've reached a non-existed page",
"Your email": "Your email", "Your email": "Your email",
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Your name will appear on your profile page and as your signature in publications, comments and responses" "Your name will appear on your profile page and as your signature in publications, comments and responses.": "Your name will appear on your profile page and as your signature in publications, comments and responses"
} }

View File

@ -568,4 +568,4 @@
"You've successfully logged out": "Вы успешно вышли из аккаунта", "You've successfully logged out": "Вы успешно вышли из аккаунта",
"Your email": "Ваш email", "Your email": "Ваш email",
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах" "Your name will appear on your profile page and as your signature in publications, comments and responses.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах"
} }

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,44 +236,41 @@ export const SessionProvider = (props: {
}) })
// when session is loaded // when session is loaded
createEffect(() => { createEffect(
if (session()) { on(
const token = session()?.access_token session,
if (token) { (s: AuthToken) => {
if (!inboxClient.private) { if (s) {
apiClient.connect(token) const token = s?.access_token
inboxClient.connect(token) if (token) {
} if (!inboxClient.private) {
apiClient.connect(token)
try { inboxClient.connect(token)
const appdata = session()?.user.app_data
if (appdata) {
const { profile } = appdata
if (profile?.id) {
setAuthor(profile)
addAuthors([profile])
} else {
setTimeout(loadAuthor, 15)
} }
try {
const appdata = session()?.user.app_data
if (appdata) {
const { profile } = appdata
if (profile?.id) {
setAuthor(profile)
addAuthors([profile])
} else {
setTimeout(loadAuthor, 15)
}
}
} catch (e) {
console.error(e)
}
setIsSessionLoaded(true)
} else {
reset()
} }
} catch (e) {
console.error(e)
} }
},
setIsSessionLoaded(true) { defer: true },
} ),
} )
})
// when author is loaded
createEffect(() => {
if (author()) {
addAuthors([author()])
} else {
reset()
}
})
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,15 +47,17 @@ 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()
const shoutIdFromUrl = Number.parseInt(shoutId ?? '0', 10) if (shoutId) {
console.debug(`editing shout ${shoutIdFromUrl}`) const shoutIdFromUrl = Number.parseInt(shoutId ?? '0', 10)
if (shoutIdFromUrl) { console.debug(`editing shout ${shoutIdFromUrl}`)
setShoutId(shoutIdFromUrl) if (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'