2026-06-01 17:15:01 -04:00
import React , { useEffect , useState } from 'react' ;
2026-06-01 21:30:27 -04:00
import { Box , Text , IconButton , Icon , Icons , Scroll , Button , Spinner , config , toRem } from 'folds' ;
2025-02-10 16:49:47 +11:00
import { Page , PageContent , PageHeader } from '../../../components/page' ;
import { SequenceCard } from '../../../components/sequence-card' ;
import { SequenceCardStyle } from '../styles.css' ;
import { SettingTile } from '../../../components/setting-tile' ;
2026-08-02 20:22:58 -04:00
import { getOriginBaseUrl , withOriginBaseUrl } from '../../../pages/pathUtils' ;
2026-05-13 23:03:14 -04:00
import pkg from '../../../../../package.json' ;
2025-02-10 16:49:47 +11:00
import { clearCacheAndReload } from '../../../../client/initMatrix' ;
import { useMatrixClient } from '../../../hooks/useMatrixClient' ;
2026-06-01 17:15:01 -04:00
2026-08-02 20:22:58 -04:00
const LotusLogo = withOriginBaseUrl ( getOriginBaseUrl (), '/public/res/Lotus.png' );
2026-08-02 15:52:04 -04:00
2026-06-01 17:15:01 -04:00
type MSC1929Contact = {
matrix_id? : string ;
email_address? : string ;
role? : string ;
};
type MSC1929Support = {
contacts? : MSC1929Contact [];
support_page? : string ;
};
2026-06-01 21:30:27 -04:00
function isMSC1929Support ( data : unknown ) : data is MSC1929Support {
if ( typeof data !== 'object' || data === null ) return false ;
const d = data as Record < string , unknown >;
if ( 'contacts' in d && ! Array . isArray ( d . contacts )) return false ;
if ( 'support_page' in d && typeof d . support_page !== 'string' ) return false ;
return true ;
}
function formatRole ( role? : string ) : string {
if ( ! role ) return 'Contact' ;
if ( role === 'm.role.admin' ) return 'Admin' ;
if ( role === 'm.role.security' ) return 'Security' ;
return role . startsWith ( 'm.role.' ) ? role . slice ( 7 ) : role ;
}
function useServerSupport () : { support : MSC1929Support | null ; loading : boolean } {
2026-06-01 17:15:01 -04:00
const mx = useMatrixClient ();
const [ support , setSupport ] = useState < MSC1929Support | null >( null );
2026-06-01 21:30:27 -04:00
const [ loading , setLoading ] = useState ( true );
2026-06-01 17:15:01 -04:00
useEffect (() => {
2026-06-01 21:30:27 -04:00
const controller = new AbortController ();
2026-07-24 17:00:36 -04:00
// MSC1929 support info is served from the MXID server-name host (like
// /.well-known/matrix/client), which on delegated/split-domain servers is
// NOT the client-API URL. Derive it from the user's domain.
const serverName = mx . getDomain ();
if ( ! serverName ) {
setLoading ( false );
return undefined ;
}
2026-06-01 21:30:27 -04:00
setLoading ( true );
2026-07-24 17:00:36 -04:00
fetch ( `https:// ${ serverName } /.well-known/matrix/support` , { signal : controller.signal })
2026-06-01 17:15:01 -04:00
. then (( res ) => {
if ( ! res . ok ) return null ;
2026-06-01 21:30:27 -04:00
return res . json ();
2026-06-01 17:15:01 -04:00
})
. then (( data ) => {
2026-06-01 21:30:27 -04:00
if ( isMSC1929Support ( data ) && ( data . contacts ? . length || data . support_page )) {
2026-06-01 17:15:01 -04:00
setSupport ( data );
}
})
2026-06-01 21:30:27 -04:00
. catch (( e ) => {
if ( e . name !== 'AbortError' ) {
// Graceful degradation — server may not have this configured
}
})
. finally (() => setLoading ( false ));
return () => controller . abort ();
2026-06-01 17:15:01 -04:00
}, [ mx ]);
2026-06-01 21:30:27 -04:00
return { support , loading };
2026-06-01 17:15:01 -04:00
}
2025-02-10 16:49:47 +11:00
type AboutProps = {
requestClose : () => void ;
};
export function About ({ requestClose } : AboutProps ) {
const mx = useMatrixClient ();
2026-06-01 21:30:27 -04:00
const { support : serverSupport , loading : supportLoading } = useServerSupport ();
2025-02-10 16:49:47 +11:00
return (
< Page >
< PageHeader outlined = { false }>
< Box grow = "Yes" gap = "200" >
< Box grow = "Yes" alignItems = "Center" gap = "200" >
< Text size = "H3" truncate >
About
</ Text >
</ Box >
< Box shrink = "No" >
2026-05-20 21:54:33 -04:00
< IconButton onClick = { requestClose } variant = "Surface" aria-label = "Close" >
2025-02-10 16:49:47 +11:00
< Icon src = { Icons . Cross } />
</ IconButton >
</ Box >
</ Box >
</ PageHeader >
< Box grow = "Yes" >
< Scroll hideTrack visibility = "Hover" >
< PageContent >
< Box direction = "Column" gap = "700" >
< Box gap = "400" >
< Box shrink = "No" >
< img
style = {{ width : toRem ( 60 ), height : toRem ( 60 ) }}
2026-05-13 19:53:29 -04:00
src = { LotusLogo }
2026-05-13 17:56:39 -04:00
alt = "Lotus Chat logo"
2025-02-10 16:49:47 +11:00
/>
</ Box >
< Box direction = "Column" gap = "300" >
< Box direction = "Column" gap = "100" >
< Box gap = "100" alignItems = "End" >
2026-05-13 19:53:29 -04:00
< Text size = "H3" > Lotus Chat </ Text >
2026-05-13 23:03:14 -04:00
< Text size = "T200" > v { pkg . version }</ Text >
2025-02-10 16:49:47 +11:00
</ Box >
2026-05-13 19:53:29 -04:00
< Text > A Matrix client for Lotus Guild .</ Text >
2025-02-10 16:49:47 +11:00
</ Box >
< Box gap = "200" wrap = "Wrap" >
< Button
as = "a"
2026-05-13 17:56:39 -04:00
href = "https://code.lotusguild.org/LotusGuild/cinny"
2025-02-10 16:49:47 +11:00
rel = "noreferrer noopener"
target = "_blank"
variant = "Secondary"
fill = "Soft"
size = "300"
radii = "300"
before = {< Icon src = { Icons . Code } size = "100" filled />}
>
< Text size = "B300" > Source Code </ Text >
</ Button >
< Button
as = "a"
2026-05-13 17:56:39 -04:00
href = "https://matrix.lotusguild.org"
2025-02-10 16:49:47 +11:00
rel = "noreferrer noopener"
target = "_blank"
variant = "Critical"
fill = "Soft"
size = "300"
radii = "300"
before = {< Icon src = { Icons . Heart } size = "100" filled />}
>
< Text size = "B300" > Support </ Text >
</ Button >
</ Box >
</ Box >
</ Box >
< Box direction = "Column" gap = "100" >
< Text size = "L400" > Options </ Text >
< SequenceCard
className = { SequenceCardStyle }
variant = "SurfaceVariant"
direction = "Column"
gap = "400"
>
< SettingTile
title = "Clear Cache & Reload"
2026-09-20 14:53:42 -04:00
description = "Clear all your locally stored data and reload from server. This also deletes this device's encryption keys — older encrypted messages stay unreadable here unless key backup is set up."
2025-02-10 16:49:47 +11:00
after = {
< Button
onClick = {() => clearCacheAndReload ( mx )}
variant = "Secondary"
fill = "Soft"
size = "300"
radii = "300"
outlined
>
< Text size = "B300" > Clear Cache </ Text >
</ Button >
}
/>
</ SequenceCard >
</ Box >
2026-06-01 21:30:27 -04:00
{( serverSupport || supportLoading ) && (
2026-06-01 17:15:01 -04:00
< Box direction = "Column" gap = "100" >
< Text size = "L400" > Homeserver Support </ Text >
< SequenceCard
className = { SequenceCardStyle }
variant = "SurfaceVariant"
direction = "Column"
gap = "400"
>
< Box direction = "Column" gap = "200" style = {{ padding : config.space.S200 }}>
2026-06-01 21:30:27 -04:00
{ supportLoading && ! serverSupport && (
< Box alignItems = "Center" gap = "200" >
< Spinner size = "100" variant = "Secondary" />
2026-06-01 21:38:35 -04:00
< Text size = "T300" priority = "300" >
Loading support info …
</ Text >
2026-06-01 21:30:27 -04:00
</ Box >
)}
{ serverSupport ? . contacts && serverSupport . contacts . length > 0 && (
2026-06-01 17:15:01 -04:00
< Box direction = "Column" gap = "100" >
{ serverSupport . contacts . map (( contact , i ) => (
2026-06-01 21:30:27 -04:00
< Box key = { i } direction = "Column" gap = "100" >
2026-06-01 17:15:01 -04:00
< Text size = "T300" priority = "300" >
2026-06-01 21:30:27 -04:00
{ formatRole ( contact . role )} :
2026-06-01 17:15:01 -04:00
</ Text >
2026-06-01 21:38:35 -04:00
< Box
direction = "Column"
gap = "100"
style = {{ paddingLeft : config.space.S200 }}
>
2026-06-01 21:30:27 -04:00
{ contact . matrix_id && (
< Text
as = "a"
href = { `https://matrix.to/#/ ${ encodeURIComponent ( contact . matrix_id ) } ` }
target = "_blank"
rel = "noreferrer noopener"
size = "T300"
>
{ contact . matrix_id }
</ Text >
)}
{ contact . email_address && (
2026-06-01 21:38:35 -04:00
< Text as = "a" href = { `mailto: ${ contact . email_address } ` } size = "T300" >
2026-06-01 21:30:27 -04:00
{ contact . email_address }
</ Text >
)}
</ Box >
2026-06-01 17:15:01 -04:00
</ Box >
))}
</ Box >
)}
2026-06-01 21:30:27 -04:00
{ serverSupport ? . support_page && (
2026-06-01 17:15:01 -04:00
< Box alignItems = "Center" gap = "200" >
< Text size = "T300" priority = "300" >
Support Page :
</ Text >
2026-06-01 21:30:27 -04:00
< Text
as = "a"
href = { serverSupport . support_page }
target = "_blank"
rel = "noreferrer noopener"
aria-label = "Open support page"
size = "T300"
>
{ serverSupport . support_page }
2026-06-01 17:15:01 -04:00
</ Text >
</ Box >
)}
</ Box >
</ SequenceCard >
</ Box >
)}
2025-02-10 16:49:47 +11:00
< Box direction = "Column" gap = "100" >
< Text size = "L400" > Credits </ Text >
< SequenceCard
className = { SequenceCardStyle }
variant = "SurfaceVariant"
direction = "Column"
gap = "400"
>
< Box
as = "ul"
direction = "Column"
gap = "200"
style = {{
margin : 0 ,
paddingLeft : config.space.S400 ,
}}
>
2026-06-28 12:54:30 -04:00
< li >
< Text size = "T300" >
The Lotus Chat logo is a derivative work based on the original { ' ' }
< a
href = "https://github.com/cinnyapp/cinny"
rel = "noreferrer noopener"
target = "_blank"
>
Cinny
</ a >{ ' ' }
logo by { ' ' }
< a
href = "https://github.com/ajbura"
rel = "noreferrer noopener"
target = "_blank"
>
Ajay Bura
</ a >{ ' ' }
and contributors , used under the terms of { ' ' }
< a
href = "https://creativecommons.org/licenses/by/4.0/"
rel = "noreferrer noopener"
target = "_blank"
>
CC - BY 4.0
</ a >
. The modified logo is © Lotus Guild , also under CC - BY 4.0 .
</ Text >
</ li >
< li >
< Text size = "T300" >
Lotus Chat is a fork of { ' ' }
< a
href = "https://github.com/cinnyapp/cinny"
rel = "noreferrer noopener"
target = "_blank"
>
Cinny
</ a >{ ' ' }
by Ajay Bura and contributors , used under the terms of { ' ' }
< a
href = "https://www.gnu.org/licenses/agpl-3.0.html"
rel = "noreferrer noopener"
target = "_blank"
>
AGPL - 3.0
</ a >
.
</ Text >
</ li >
2025-02-10 16:49:47 +11:00
< li >
< Text size = "T300" >
The { ' ' }
< a
href = "https://github.com/matrix-org/matrix-js-sdk"
rel = "noreferrer noopener"
target = "_blank"
>
matrix - js - sdk
</ a >{ ' ' }
is © { ' ' }
< a
href = "https://matrix.org/foundation"
rel = "noreferrer noopener"
target = "_blank"
>
The Matrix . org Foundation C . I . C
</ a >{ ' ' }
used under the terms of { ' ' }
< a
href = "http://www.apache.org/licenses/LICENSE-2.0"
rel = "noreferrer noopener"
target = "_blank"
>
Apache 2.0
</ a >
.
</ Text >
</ li >
< li >
< Text size = "T300" >
The { ' ' }
< a
href = "https://github.com/mozilla/twemoji-colr"
target = "_blank"
rel = "noreferrer noopener"
>
twemoji - colr
</ a >{ ' ' }
font is © { ' ' }
< a href = "https://mozilla.org/" target = "_blank" rel = "noreferrer noopener" >
Mozilla Foundation
</ a >{ ' ' }
used under the terms of { ' ' }
< a
href = "http://www.apache.org/licenses/LICENSE-2.0"
target = "_blank"
rel = "noreferrer noopener"
>
Apache 2.0
</ a >
.
</ Text >
</ li >
< li >
< Text size = "T300" >
The { ' ' }
< a
href = "https://twemoji.twitter.com"
target = "_blank"
rel = "noreferrer noopener"
>
Twemoji
</ a >{ ' ' }
emoji art is © { ' ' }
< a
href = "https://twemoji.twitter.com"
target = "_blank"
rel = "noreferrer noopener"
>
Twitter , Inc and other contributors
</ a >{ ' ' }
used under the terms of { ' ' }
< a
href = "https://creativecommons.org/licenses/by/4.0/"
target = "_blank"
rel = "noreferrer noopener"
>
CC - BY 4.0
</ a >
.
</ Text >
</ li >
< li >
< Text size = "T300" >
The { ' ' }
< a
href = "https://material.io/design/sound/sound-resources.html"
target = "_blank"
rel = "noreferrer noopener"
>
Material sound resources
</ a >{ ' ' }
are © { ' ' }
< a href = "https://google.com" target = "_blank" rel = "noreferrer noopener" >
Google
</ a >{ ' ' }
used under the terms of { ' ' }
< a
href = "https://creativecommons.org/licenses/by/4.0/"
target = "_blank"
rel = "noreferrer noopener"
>
CC - BY 4.0
</ a >
.
</ Text >
</ li >
</ Box >
</ SequenceCard >
</ Box >
</ Box >
</ PageContent >
</ Scroll >
</ Box >
</ Page >
);
}