From 74b790927eadfeca8b4bf816a51c87c36eba67ed Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Sun, 13 Sep 2020 17:39:39 +0100 Subject: [PATCH 01/10] Show sharer preview for matrix.to links --- src/components/DefaultPreview.scss | 4 ++ src/components/LinkPreview.tsx | 54 ++++++++++--------------- src/components/UserPreview.tsx | 64 ++++++++++++++++++++++++------ src/contexts/HSContext.ts | 16 +------- src/utils/getHS.ts | 52 ++++++++++++++++++++++++ 5 files changed, 128 insertions(+), 62 deletions(-) create mode 100644 src/utils/getHS.ts diff --git a/src/components/DefaultPreview.scss b/src/components/DefaultPreview.scss index 21b77bb..48e404c 100644 --- a/src/components/DefaultPreview.scss +++ b/src/components/DefaultPreview.scss @@ -19,4 +19,8 @@ limitations under the License. border-radius: 0; border: 0; } + + h1 { + word-break: break-all; + } } diff --git a/src/components/LinkPreview.tsx b/src/components/LinkPreview.tsx index bcdb7be..618de53 100644 --- a/src/components/LinkPreview.tsx +++ b/src/components/LinkPreview.tsx @@ -20,10 +20,11 @@ import { getEvent, client } from 'matrix-cypher'; import { RoomPreviewWithTopic } from './RoomPreview'; import InviteTile from './InviteTile'; import { SafeLink, LinkKind } from '../parser/types'; -import UserPreview from './UserPreview'; +import UserPreview, { WrappedInviterPreview } from './UserPreview'; import EventPreview from './EventPreview'; import HomeserverOptions from './HomeserverOptions'; import DefaultPreview from './DefaultPreview'; +import Toggle from './Toggle'; import { clientMap } from '../clients'; import { getRoomFromId, @@ -32,12 +33,7 @@ import { getUser, } from '../utils/cypher-wrapper'; import { ClientContext } from '../contexts/ClientContext'; -import HSContext, { - TempHSContext, - HSOptions, - State as HSState, -} from '../contexts/HSContext'; -import Toggle from './Toggle'; +import useHSs from '../utils/getHS'; interface IProps { link: SafeLink; @@ -118,32 +114,14 @@ const Preview: React.FC = ({ link, client }: PreviewProps) => { return content; }; -function selectedClient(link: SafeLink, hsOptions: HSState): string[] { - switch (hsOptions.option) { - case HSOptions.Unset: - return []; - case HSOptions.None: - return []; - case HSOptions.TrustedHSOnly: - return [hsOptions.hs]; - case HSOptions.Any: - return [ - 'https://' + link.identifier.split(':')[1], - ...link.arguments.vias, - ]; - } -} - const LinkPreview: React.FC = ({ link }: IProps) => { let content: JSX.Element; const [showHSOptions, setShowHSOPtions] = useState(false); - const [hsOptions] = useContext(HSContext); - const [tempHSState] = useContext(TempHSContext); - if ( - hsOptions.option === HSOptions.Unset && - tempHSState.option === HSOptions.Unset - ) { + const hses = useHSs(link); + console.log(hses); + + if (!hses) { content = ( <> @@ -164,11 +142,7 @@ const LinkPreview: React.FC = ({ link }: IProps) => { ); } } else { - const clients = - tempHSState.option !== HSOptions.Unset - ? selectedClient(link, tempHSState) - : selectedClient(link, hsOptions); - content = ; + content = ; } const [{ clientId }] = useContext(ClientContext); @@ -182,8 +156,20 @@ const LinkPreview: React.FC = ({ link }: IProps) => { const client = displayClientId ? clientMap[displayClientId] : null; + const sharer = link.arguments.sharer ? ( + + ) : null; + return ( + {sharer} {content} ); diff --git a/src/components/UserPreview.tsx b/src/components/UserPreview.tsx index 415b2a2..af9a440 100644 --- a/src/components/UserPreview.tsx +++ b/src/components/UserPreview.tsx @@ -14,10 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import { User } from 'matrix-cypher'; +import React, { useState, useEffect } from 'react'; +import { client, User, getUserDetails } from 'matrix-cypher'; +import icon from '../imgs/chat-icon.svg'; -import { UserAvatar } from './Avatar'; +import Avatar, { UserAvatar } from './Avatar'; +import useHSs from '../utils/getHS'; +import { UserId } from '../parser/types'; import './UserPreview.scss'; @@ -37,14 +40,49 @@ const UserPreview: React.FC = ({ user, userId }: IProps) => ( export default UserPreview; -export const InviterPreview: React.FC = ({ user, userId }: IProps) => ( -
-
-

- Invited by {user.displayname} -

-

{userId}

-
+interface InviterPreviewProps { + user?: User; + userId: string; +} + +export const InviterPreview: React.FC = ({ + user, + userId, +}: InviterPreviewProps) => { + const avatar = user ? ( -
-); + ) : ( + + ); + return ( +
+
+

+ Invited by {user ? user.displayname : userId} +

+ {user ?

{userId}

: null} +
+ {avatar} +
+ ); +}; + +interface WrappedInviterProps { + link: UserId; +} + +export const WrappedInviterPreview: React.FC = ({ + link, +}: WrappedInviterProps) => { + const [user, setUser] = useState(undefined); + const hss = useHSs(link); + useEffect(() => { + if (hss) { + client(hss[0]) + .then((c) => getUserDetails(c, link.identifier)) + .then(setUser) + .catch((x) => console.log("couldn't fetch user preview", x)); + } + }, [hss, link]); + return ; +}; diff --git a/src/contexts/HSContext.ts b/src/contexts/HSContext.ts index 686ba05..c6b5201 100644 --- a/src/contexts/HSContext.ts +++ b/src/contexts/HSContext.ts @@ -29,17 +29,12 @@ export enum HSOptions { TrustedHSOnly = 'TRUSTED_CLIENT_ONLY', // Matrix.to may contact any homeserver it requires Any = 'ANY', - // Matrix.to may not contact any homeservers - None = 'NONE', } const STATE_SCHEMA = union([ object({ option: literal(HSOptions.Unset), }), - object({ - option: literal(HSOptions.None), - }), object({ option: literal(HSOptions.Any), }), @@ -55,7 +50,6 @@ export type State = TypeOf; export enum ActionType { SetHS = 'SET_HS', SetAny = 'SET_ANY', - SetNone = 'SET_NONE', } export interface SetHS { @@ -67,11 +61,7 @@ export interface SetAny { action: ActionType.SetAny; } -export interface SetNone { - action: ActionType.SetNone; -} - -export type Action = SetHS | SetAny | SetNone; +export type Action = SetHS | SetAny; export const INITIAL_STATE: State = { option: HSOptions.Unset, @@ -81,10 +71,6 @@ export const unpersistedReducer = (state: State, action: Action): State => { console.log('reducing'); console.log(action); switch (action.action) { - case ActionType.SetNone: - return { - option: HSOptions.None, - }; case ActionType.SetAny: return { option: HSOptions.Any, diff --git a/src/utils/getHS.ts b/src/utils/getHS.ts new file mode 100644 index 0000000..b8239da --- /dev/null +++ b/src/utils/getHS.ts @@ -0,0 +1,52 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { useContext } from 'react'; +import HSContext, { + TempHSContext, + State, + HSOptions, +} from '../contexts/HSContext'; +import { SafeLink } from '../parser/types'; + +function selectedClient(link: SafeLink, hsOptions: State): string[] { + switch (hsOptions.option) { + case HSOptions.Unset: + return []; + case HSOptions.TrustedHSOnly: + return [hsOptions.hs]; + case HSOptions.Any: + return [ + ...link.identifier + .split('/') + .map((i) => 'https://' + i.split(':')[1]), + ...link.arguments.vias, + ]; + } +} + +export default function useHSs(link: SafeLink): string[] { + const [HSState] = useContext(HSContext); + const [TempHSState] = useContext(TempHSContext); + + if (HSState.option !== HSOptions.Unset) { + return selectedClient(link, HSState); + } else if (TempHSState.option !== HSOptions.Unset) { + return selectedClient(link, TempHSState); + } else { + return []; + } +} From 5f5eb60c02e835ab70e14a12546e5c4cfad9f761 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Sun, 13 Sep 2020 17:43:22 +0100 Subject: [PATCH 02/10] Cleanup, and ally stuff --- src/components/LinkPreview.tsx | 1 - src/components/StyledCheckbox.tsx | 2 +- src/components/Toggle.tsx | 2 +- src/contexts/HSContext.ts | 2 -- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/LinkPreview.tsx b/src/components/LinkPreview.tsx index 618de53..4d0b9da 100644 --- a/src/components/LinkPreview.tsx +++ b/src/components/LinkPreview.tsx @@ -119,7 +119,6 @@ const LinkPreview: React.FC = ({ link }: IProps) => { const [showHSOptions, setShowHSOPtions] = useState(false); const hses = useHSs(link); - console.log(hses); if (!hses) { content = ( diff --git a/src/components/StyledCheckbox.tsx b/src/components/StyledCheckbox.tsx index cd39a37..9450f4e 100644 --- a/src/components/StyledCheckbox.tsx +++ b/src/components/StyledCheckbox.tsx @@ -32,7 +32,7 @@ const StyledCheckbox: React.FC = ({ {/* Using the div to center the image */}
- +
{children} diff --git a/src/components/Toggle.tsx b/src/components/Toggle.tsx index eba1952..1e34d31 100644 --- a/src/components/Toggle.tsx +++ b/src/components/Toggle.tsx @@ -28,7 +28,7 @@ const Toggle: React.FC = ({ children, ...props }: IProps) => ( ); diff --git a/src/contexts/HSContext.ts b/src/contexts/HSContext.ts index c6b5201..e19ff60 100644 --- a/src/contexts/HSContext.ts +++ b/src/contexts/HSContext.ts @@ -68,8 +68,6 @@ export const INITIAL_STATE: State = { }; export const unpersistedReducer = (state: State, action: Action): State => { - console.log('reducing'); - console.log(action); switch (action.action) { case ActionType.SetAny: return { From 471c9cd21de3433acb0382151409db5ee02bdc19 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Sun, 13 Sep 2020 18:02:18 +0100 Subject: [PATCH 03/10] Fix empty array not falsy bug --- src/components/LinkPreview.tsx | 2 +- src/components/UserPreview.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LinkPreview.tsx b/src/components/LinkPreview.tsx index 4d0b9da..511d3ac 100644 --- a/src/components/LinkPreview.tsx +++ b/src/components/LinkPreview.tsx @@ -120,7 +120,7 @@ const LinkPreview: React.FC = ({ link }: IProps) => { const hses = useHSs(link); - if (!hses) { + if (!hses.length) { content = ( <> diff --git a/src/components/UserPreview.tsx b/src/components/UserPreview.tsx index af9a440..0a88e6a 100644 --- a/src/components/UserPreview.tsx +++ b/src/components/UserPreview.tsx @@ -77,7 +77,7 @@ export const WrappedInviterPreview: React.FC = ({ const [user, setUser] = useState(undefined); const hss = useHSs(link); useEffect(() => { - if (hss) { + if (hss.length) { client(hss[0]) .then((c) => getUserDetails(c, link.identifier)) .then(setUser) From 4d456c2799ac1244cc6c5bedd2bd2bbfee7f6a37 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 16 Sep 2020 00:19:52 +0100 Subject: [PATCH 04/10] Implement design review changes --- src/App.scss | 3 +- src/App.tsx | 20 ++++-- src/components/Avatar.tsx | 2 +- src/components/Button.scss | 30 ++++++++- src/components/Button.stories.tsx | 4 ++ src/components/Button.tsx | 22 ++++++- src/components/ClientSelection.tsx | 3 +- src/components/ClientTile.scss | 22 +++---- src/components/CreateLinkTile.scss | 53 +++++++++++++++- src/components/CreateLinkTile.tsx | 53 ++++++++++++---- src/components/Footer.scss | 33 ++++++++++ src/components/Footer.tsx | 67 ++++++++++++++++++++ src/components/HomeserverOptions.scss | 1 + src/components/HomeserverOptions.stories.tsx | 12 +++- src/components/HomeserverOptions.tsx | 56 ++++++++++------ src/components/Input.scss | 16 ++++- src/components/Input.tsx | 6 +- src/components/InviteTile.scss | 5 ++ src/components/InviteTile.tsx | 16 +++-- src/components/LinkPreview.tsx | 19 +++--- src/components/MatrixTile.tsx | 31 ++++++--- src/components/Tile.scss | 3 +- src/components/Toggle.tsx | 2 +- src/components/UserPreview.scss | 14 +++- src/components/UserPreview.tsx | 7 +- src/contexts/ClientContext.ts | 2 + src/contexts/HSContext.ts | 15 +++-- src/imgs/copy.svg | 4 ++ src/imgs/link.svg | 3 + src/imgs/refresh.svg | 3 + src/imgs/tick.svg | 4 +- src/index.scss | 1 + src/pages/LinkRouter.tsx | 1 - src/utils/cypher-wrapper.ts | 31 ++++++--- 34 files changed, 450 insertions(+), 114 deletions(-) create mode 100644 src/components/Footer.scss create mode 100644 src/components/Footer.tsx create mode 100644 src/imgs/copy.svg create mode 100644 src/imgs/link.svg create mode 100644 src/imgs/refresh.svg diff --git a/src/App.scss b/src/App.scss index 0f0855e..7aff62a 100644 --- a/src/App.scss +++ b/src/App.scss @@ -20,6 +20,7 @@ limitations under the License. background-color: $app-background; background-image: url('./imgs/background.svg'); background-repeat: no-repeat; + background-size: stretch; background-position: 50% -20%; } @@ -32,7 +33,7 @@ limitations under the License. .topSpacer { @include spacer; - height: 20vh; + height: 10vh; } .bottomSpacer { diff --git a/src/App.tsx b/src/App.tsx index 5f60456..c9205ba 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,6 +21,7 @@ import CreateLinkTile from './components/CreateLinkTile'; import MatrixTile from './components/MatrixTile'; import Tile from './components/Tile'; import LinkRouter from './pages/LinkRouter'; +import Footer from './components/Footer'; import './App.scss'; @@ -32,7 +33,6 @@ const App: React.FC = () => { let page = ( <> -
); @@ -50,12 +50,18 @@ const App: React.FC = () => { } return ( - -
- {page} - -
- + + +
+ {page} +
+ +
+
+
+
+ + ); }; diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 003d004..00c5183 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -19,7 +19,7 @@ import classNames from 'classnames'; import { Room, User } from 'matrix-cypher'; import { getMediaQueryFromMCX } from '../utils/cypher-wrapper'; -import logo from '../imgs/matrix-logo.svg'; +import logo from '../imgs/chat-icon.svg'; import './Avatar.scss'; diff --git a/src/components/Button.scss b/src/components/Button.scss index c34c521..c1a6d70 100644 --- a/src/components/Button.scss +++ b/src/components/Button.scss @@ -14,12 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -@import "../color-scheme"; +@import '../color-scheme'; .button { width: 100%; - padding: 1rem; + height: 48px; + border-radius: 2rem; border: 0; @@ -28,6 +29,31 @@ limitations under the License. font-size: 14px; font-weight: 500; + + &:hover { + cursor: pointer; + } + + position: relative; + + .buttonIcon { + position: absolute; + height: 24px; + width: 24px; + + left: 18px; + top: 12px; + } +} + +.buttonSecondary { + background-color: $background; + color: $foreground; + border: 1px solid $foreground; +} + +.errorButton:hover { + cursor: not-allowed; } .buttonHighlight { diff --git a/src/components/Button.stories.tsx b/src/components/Button.stories.tsx index 1f5d2e5..25cfee8 100644 --- a/src/components/Button.stories.tsx +++ b/src/components/Button.stories.tsx @@ -27,3 +27,7 @@ export const WithText: React.FC = () => ( {text('label', 'Hello Story Book')} ); + +export const Secondary: React.FC = () => ( + +); diff --git a/src/components/Button.tsx b/src/components/Button.tsx index a347254..fd031f2 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -22,6 +22,9 @@ import './Button.scss'; interface IProps extends React.ButtonHTMLAttributes { // Briefly display these instead of the children onClick flashChildren?: React.ReactNode; + secondary?: boolean; + icon?: string; + flashIcon?: string; } /** @@ -31,7 +34,16 @@ const Button: React.FC< IProps & React.RefAttributes > = React.forwardRef( ( - { onClick, children, flashChildren, className, ...props }: IProps, + { + onClick, + children, + flashChildren, + className, + secondary, + icon, + flashIcon, + ...props + }: IProps, ref: React.Ref ) => { const [wasClicked, setWasClicked] = React.useState(false); @@ -51,8 +63,15 @@ const Button: React.FC< const classNames = classnames('button', className, { buttonHighlight: wasClicked, + buttonSecondary: secondary, }); + const iconSrc = wasClicked && flashIcon ? flashIcon : icon; + + const buttonIcon = icon ? ( + + ) : null; + return ( ); diff --git a/src/components/ClientSelection.tsx b/src/components/ClientSelection.tsx index bb58642..9989b29 100644 --- a/src/components/ClientSelection.tsx +++ b/src/components/ClientSelection.tsx @@ -38,7 +38,7 @@ const ClientSelection: React.FC = ({ link }: IProps) => { }} checked={rememberSelection} > - Remember my selection for future invites in this browser + Remember choice for future invites in this browser { @@ -79,7 +79,6 @@ const ClientSelection: React.FC = ({ link }: IProps) => { return (
{options} -

Clients you can accept this invite with

{clearSelection}
diff --git a/src/components/ClientTile.scss b/src/components/ClientTile.scss index 0b8f091..2beb41a 100644 --- a/src/components/ClientTile.scss +++ b/src/components/ClientTile.scss @@ -19,7 +19,7 @@ limitations under the License. .clientTile { display: flex; flex-direction: row; - align-items: center; + align-items: flex-start; min-height: 150px; width: 100%; @@ -28,7 +28,10 @@ limitations under the License. > img { flex-shrink: 0; - height: 130px; + height: 116px; + width: 116px; + margin-right: 14px; + border-radius: 16px; } > div { @@ -47,11 +50,10 @@ limitations under the License. } .button { - margin: 5px; + width: 50%; } } - border: 1px solid $borders; border-radius: 8px; padding: 15px; @@ -59,8 +61,8 @@ limitations under the License. // For the chevron position: relative; - &::hover { - background-color: $grey; + &:hover { + background-color: $app-background; } } @@ -68,12 +70,4 @@ limitations under the License. position: relative; width: 100%; - - &::after { - // TODO: add chevron top right - position: absolute; - right: 10px; - top: 5px; - content: '>'; - } } diff --git a/src/components/CreateLinkTile.scss b/src/components/CreateLinkTile.scss index ada3856..5a300ce 100644 --- a/src/components/CreateLinkTile.scss +++ b/src/components/CreateLinkTile.scss @@ -29,7 +29,6 @@ limitations under the License. display: grid; row-gap: 24px; align-self: center; - padding: 0 30px; } > a { @@ -39,4 +38,56 @@ limitations under the License. h1 { word-break: break-all; } + + .createLinkReset { + height: 40px; + width: 40px; + + border-radius: 100%; + border: 1px solid lighten($grey, 50%); + + background: $background; + + padding: 6px; + + position: relative; + + > div { + // This is a terrible case of faking it till + // we make it. It will break. I'm so sorry + position: absolute; + display: none; + + width: max-content; + top: -35px; + left: -17px; + + border-radius: 30px; + padding: 5px 15px; + + background: $background; + + word-wrap: none; + } + + img { + height: 100%; + width: 100%; + border: 0; + + filter: invert(12%); + } + + &:hover { + border: 0; + + background: $foreground; + + cursor: pointer; + + > div { + display: block; + } + } + } } diff --git a/src/components/CreateLinkTile.tsx b/src/components/CreateLinkTile.tsx index 51a79eb..e133ed4 100644 --- a/src/components/CreateLinkTile.tsx +++ b/src/components/CreateLinkTile.tsx @@ -19,11 +19,13 @@ import { Formik, Form } from 'formik'; import Tile from './Tile'; import Button from './Button'; -import TextButton from './TextButton'; import Input from './Input'; import { parseHash } from '../parser/parser'; import { LinkKind } from '../parser/types'; - +import linkIcon from '../imgs/link.svg'; +import copyIcon from '../imgs/copy.svg'; +import tickIcon from '../imgs/tick.svg'; +import refreshIcon from '../imgs/refresh.svg'; import './CreateLinkTile.scss'; interface ILinkNotCreatedTileProps { @@ -38,11 +40,16 @@ interface FormValues { function validate(values: FormValues): Partial { const errors: Partial = {}; + if (values.identifier === '') { + errors.identifier = ''; + return errors; + } + const parse = parseHash(values.identifier); if (parse.kind === LinkKind.ParseFailed) { errors.identifier = - "That link doesn't look right. Double check the details."; + "That identifier doesn't look right. Double check the details."; } return errors; @@ -72,14 +79,26 @@ const LinkNotCreatedTile: React.FC = ( ); }} > -
- - -
+ {(formik): JSX.Element => ( +
+ + +
+ )} ); @@ -102,14 +121,20 @@ const LinkCreatedTile: React.FC = (props) => { return ( - props.setLink('')}> - Create another lnk - +

{props.link}

- + {({ values, errors }): JSX.Element => ( +
+ + {values.HSUrl && !errors.HSUrl ? ( + + ) : null} +
+ )} - ) : null; + ); return (
+

About {link.identifier}

- Let's locate a homeserver to show you more information. + Select a homeserver to learn more about{' '} + {link.identifier}.
+ The homeserver will provide metadata about the link such + as an avatar or description. Homeservers will be able to + relate your ip to resources you've opened invites for in + matrix.to

= () => { Remember my choice. - setUsePrefered(!usePrefered)} - > - Use my prefered homeserver only - + {hsInput} ); diff --git a/src/components/Input.scss b/src/components/Input.scss index e6ca735..f052af1 100644 --- a/src/components/Input.scss +++ b/src/components/Input.scss @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -@import "../color-scheme"; -@import "../error"; +@import '../color-scheme'; +@import '../error'; .input { width: 100%; @@ -23,7 +23,8 @@ limitations under the License. background: $background; - border: 1px solid $font; + border: 1px solid $foreground; + font: lighten($grey, 60%); border-radius: 24px; font-size: 14px; @@ -32,9 +33,18 @@ limitations under the License. &.error { @include error; } + + &:focus { + border: 1px solid $font; + font: $font; + } } .inputError { @include error; text-align: center; } + +.inputMuted { + border-color: lighten($grey, 60%); +} diff --git a/src/components/Input.tsx b/src/components/Input.tsx index 7a50385..b094061 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -20,12 +20,13 @@ import { useField } from 'formik'; import './Input.scss'; -interface IProps extends React.InputHTMLAttributes { +interface IProps extends React.InputHTMLAttributes { name: string; type: string; + muted?: boolean; } -const Input: React.FC = ({ className, ...props }) => { +const Input: React.FC = ({ className, muted, ...props }) => { const [field, meta] = useField(props); const error = @@ -35,6 +36,7 @@ const Input: React.FC = ({ className, ...props }) => { const classNames = classnames('input', className, { error: meta.error, + inputMuted: !!muted, }); return ( diff --git a/src/components/InviteTile.scss b/src/components/InviteTile.scss index 0421927..6d23632 100644 --- a/src/components/InviteTile.scss +++ b/src/components/InviteTile.scss @@ -25,4 +25,9 @@ limitations under the License. justify-content: space-between; row-gap: 20px; } + + hr { + width: 100%; + margin: 0; + } } diff --git a/src/components/InviteTile.tsx b/src/components/InviteTile.tsx index a35b2c7..fad18a4 100644 --- a/src/components/InviteTile.tsx +++ b/src/components/InviteTile.tsx @@ -25,7 +25,6 @@ import ClientSelection from './ClientSelection'; import { Client, ClientKind } from '../clients/types'; import { SafeLink } from '../parser/types'; import TextButton from './TextButton'; -import FakeProgress from './FakeProgress'; interface IProps { children?: React.ReactNode; @@ -39,10 +38,8 @@ const InviteTile: React.FC = ({ children, client, link }: IProps) => { let advanced: React.ReactNode; if (client === null) { - invite = showAdvanced ? ( - - ) : ( - ); @@ -89,7 +86,9 @@ const InviteTile: React.FC = ({ children, client, link }: IProps) => { if (client === null) { advanced = ( <> -

Pick an app to accept the invite with

+
+

Almost done!

+

Pick a client to open {link.identifier}

); @@ -104,12 +103,15 @@ const InviteTile: React.FC = ({ children, client, link }: IProps) => { } } + advanced = advanced ? ( +
{advanced}
+ ) : null; return ( <> {children} {invite} -
{advanced}
+ {advanced}
); diff --git a/src/components/LinkPreview.tsx b/src/components/LinkPreview.tsx index 511d3ac..58c0fff 100644 --- a/src/components/LinkPreview.tsx +++ b/src/components/LinkPreview.tsx @@ -47,13 +47,12 @@ const invite = async ({ link: SafeLink; }): Promise => { // TODO: replace with client fetch - const defaultClient = await client(clientAddress); switch (link.kind) { case LinkKind.Alias: return ( ); @@ -61,14 +60,14 @@ const invite = async ({ case LinkKind.RoomId: return ( ); case LinkKind.UserId: return ( ); @@ -76,10 +75,10 @@ const invite = async ({ case LinkKind.Permalink: return ( = ({ link }: IProps) => { checked={showHSOptions} onChange={(): void => setShowHSOPtions(!showHSOptions)} > - Show more information + About {link.identifier} ); @@ -136,7 +135,7 @@ const LinkPreview: React.FC = ({ link }: IProps) => { content = ( <> {content} - + ); } @@ -164,7 +163,9 @@ const LinkPreview: React.FC = ({ link }: IProps) => { originalLink: '', }} /> - ) : null; + ) : ( +

You're invited to join

+ ); return ( diff --git a/src/components/MatrixTile.tsx b/src/components/MatrixTile.tsx index ee7ebaa..643e830 100644 --- a/src/components/MatrixTile.tsx +++ b/src/components/MatrixTile.tsx @@ -21,15 +21,30 @@ import logo from '../imgs/matrix-logo.svg'; import './MatrixTile.scss'; -const MatrixTile: React.FC = () => { +interface IProps { + isLink?: boolean; +} + +const MatrixTile: React.FC = ({ isLink }: IProps) => { + const copy = isLink ? ( +
+ This invite uses Matrix, an open + network for secure, decentralized communication. +
+ ) : ( +
+ Matrix.to is a stateless URL redirecting service for the{' '} + Matrix ecosystem. +
+ ); + return ( - - matrix-logo -
- This invite uses Matrix, an - open network for secure, decentralized communication. -
-
+
+ + matrix-logo + {copy} + +
); }; diff --git a/src/components/Tile.scss b/src/components/Tile.scss index a18f8ad..a5dabae 100644 --- a/src/components/Tile.scss +++ b/src/components/Tile.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -@import "../color-scheme"; +@import '../color-scheme'; .tile { background-color: $background; @@ -30,4 +30,5 @@ limitations under the License. p { color: $grey; } + transition: width 2s, height 2s, transform 2s; } diff --git a/src/components/Toggle.tsx b/src/components/Toggle.tsx index 1e34d31..496a758 100644 --- a/src/components/Toggle.tsx +++ b/src/components/Toggle.tsx @@ -21,7 +21,7 @@ import chevron from '../imgs/chevron-down.svg'; import './Toggle.scss'; interface IProps extends React.InputHTMLAttributes { - children?: React.ReactChild; + children?: React.ReactNode; } const Toggle: React.FC = ({ children, ...props }: IProps) => ( diff --git a/src/components/UserPreview.scss b/src/components/UserPreview.scss index d35dfda..3ff72b6 100644 --- a/src/components/UserPreview.scss +++ b/src/components/UserPreview.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -@import "../color-scheme"; +@import '../color-scheme'; .userPreview { width: 100%; @@ -70,5 +70,17 @@ limitations under the License. .avatar { flex-grow: 0; flex-shrink: 0; + height: 32px; + width: 32px; + } + + &.centeredMiniUserPreview { + h1 { + width: unset; + text-align: center; + } + img { + display: none; + } } } diff --git a/src/components/UserPreview.tsx b/src/components/UserPreview.tsx index 0a88e6a..6f90e19 100644 --- a/src/components/UserPreview.tsx +++ b/src/components/UserPreview.tsx @@ -16,6 +16,7 @@ limitations under the License. import React, { useState, useEffect } from 'react'; import { client, User, getUserDetails } from 'matrix-cypher'; +import classNames from 'classnames'; import icon from '../imgs/chat-icon.svg'; import Avatar, { UserAvatar } from './Avatar'; @@ -54,8 +55,12 @@ export const InviterPreview: React.FC = ({ ) : ( ); + const className = classNames('miniUserPreview', { + centeredMiniUserPreview: !user, + }); + return ( -
+

Invited by {user ? user.displayname : userId} diff --git a/src/contexts/ClientContext.ts b/src/contexts/ClientContext.ts index 0c78807..6fb364b 100644 --- a/src/contexts/ClientContext.ts +++ b/src/contexts/ClientContext.ts @@ -101,6 +101,8 @@ export const ClientContext = React.createContext< [State, React.Dispatch] >([initialState, (): void => {}]); +export default ClientContext; + // Quick rename to make importing easier export const ClientProvider = ClientContext.Provider; export const ClientConsumer = ClientContext.Consumer; diff --git a/src/contexts/HSContext.ts b/src/contexts/HSContext.ts index e19ff60..1203f3f 100644 --- a/src/contexts/HSContext.ts +++ b/src/contexts/HSContext.ts @@ -50,6 +50,7 @@ export type State = TypeOf; export enum ActionType { SetHS = 'SET_HS', SetAny = 'SET_ANY', + Clear = 'CLEAR', } export interface SetHS { @@ -61,13 +62,17 @@ export interface SetAny { action: ActionType.SetAny; } -export type Action = SetHS | SetAny; +export interface Clear { + action: ActionType.Clear; +} + +export type Action = SetHS | SetAny | Clear; export const INITIAL_STATE: State = { option: HSOptions.Unset, }; -export const unpersistedReducer = (state: State, action: Action): State => { +export const unpersistedReducer = (_state: State, action: Action): State => { switch (action.action) { case ActionType.SetAny: return { @@ -78,8 +83,10 @@ export const unpersistedReducer = (state: State, action: Action): State => { option: HSOptions.TrustedHSOnly, hs: action.HSURL, }; - default: - return state; + case ActionType.Clear: + return { + option: HSOptions.Unset, + }; } }; diff --git a/src/imgs/copy.svg b/src/imgs/copy.svg new file mode 100644 index 0000000..7906974 --- /dev/null +++ b/src/imgs/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/imgs/link.svg b/src/imgs/link.svg new file mode 100644 index 0000000..f41a673 --- /dev/null +++ b/src/imgs/link.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/imgs/refresh.svg b/src/imgs/refresh.svg new file mode 100644 index 0000000..5463124 --- /dev/null +++ b/src/imgs/refresh.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/imgs/tick.svg b/src/imgs/tick.svg index b49f4a4..b459490 100644 --- a/src/imgs/tick.svg +++ b/src/imgs/tick.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/index.scss b/src/index.scss index a9b22d8..0fcbaa8 100644 --- a/src/index.scss +++ b/src/index.scss @@ -47,6 +47,7 @@ h1 { font-size: 24px; line-height: 32px; text-align: center; + color: $foreground; } h4 { diff --git a/src/pages/LinkRouter.tsx b/src/pages/LinkRouter.tsx index a455c7d..ae797aa 100644 --- a/src/pages/LinkRouter.tsx +++ b/src/pages/LinkRouter.tsx @@ -53,7 +53,6 @@ const LinkRouter: React.FC = ({ link }: IProps) => { feedback = ( <> -
{client} ); diff --git a/src/utils/cypher-wrapper.ts b/src/utils/cypher-wrapper.ts index 5a43a4c..5155d20 100644 --- a/src/utils/cypher-wrapper.ts +++ b/src/utils/cypher-wrapper.ts @@ -20,6 +20,7 @@ limitations under the License. import { Client, + client, Room, RoomAlias, User, @@ -59,7 +60,8 @@ export const fallbackRoom = ({ const roomAlias_ = roomAlias ? roomAlias : identifier; return { aliases: [roomAlias_], - topic: 'Unable to find room details.', + topic: + 'No details available. This might be a private room. You can still join below.', canonical_alias: roomAlias_, name: roomAlias_, num_joined_members: 0, @@ -75,18 +77,24 @@ export const fallbackRoom = ({ * a `fallbackRoom` */ export async function getRoomFromAlias( - client: Client, + clientURL: string, roomAlias: string ): Promise { let resolvedRoomAlias: RoomAlias; + let resolvedClient: Client; + try { - resolvedRoomAlias = await getRoomIdFromAlias(client, roomAlias); + resolvedClient = await client(clientURL); + resolvedRoomAlias = await getRoomIdFromAlias(resolvedClient, roomAlias); } catch { return fallbackRoom({ identifier: roomAlias }); } try { - return await searchPublicRooms(client, resolvedRoomAlias.room_id); + return await searchPublicRooms( + resolvedClient, + resolvedRoomAlias.room_id + ); } catch { return fallbackRoom({ identifier: roomAlias, @@ -101,11 +109,12 @@ export async function getRoomFromAlias( * a `fallbackRoom` */ export async function getRoomFromId( - client: Client, + clientURL: string, roomId: string ): Promise { try { - return await searchPublicRooms(client, roomId); + const resolvedClient = await client(clientURL); + return await searchPublicRooms(resolvedClient, roomId); } catch { return fallbackRoom({ identifier: roomId }); } @@ -114,9 +123,13 @@ export async function getRoomFromId( /* * Tries to fetch user details. If it fails it uses a `fallbackUser` */ -export async function getUser(client: Client, userId: string): Promise { +export async function getUser( + clientURL: string, + userId: string +): Promise { try { - return await getUserDetails(client, userId); + const resolvedClient = await client(clientURL); + return await getUserDetails(resolvedClient, userId); } catch { return fallbackUser(userId); } @@ -127,7 +140,7 @@ export async function getUser(client: Client, userId: string): Promise { * a `fallbackRoom` */ export async function getRoomFromPermalink( - client: Client, + client: string, link: Permalink ): Promise { switch (link.roomKind) { From c000c17a3b2f156eb50bda44612b6bca50d67934 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 16 Sep 2020 12:46:16 +0100 Subject: [PATCH 05/10] Nit picks --- src/components/CreateLinkTile.tsx | 1 + src/components/Footer.tsx | 4 +--- src/components/HomeserverOptions.tsx | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/CreateLinkTile.tsx b/src/components/CreateLinkTile.tsx index e133ed4..12ffd54 100644 --- a/src/components/CreateLinkTile.tsx +++ b/src/components/CreateLinkTile.tsx @@ -78,6 +78,7 @@ const LinkNotCreatedTile: React.FC = ( values.identifier ); }} + validateOnChange={false} > {(formik): JSX.Element => (
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index eef9cf4..e8578e5 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -52,9 +52,7 @@ const Footer: React.FC = () => { return ( Date: Wed, 16 Sep 2020 13:04:18 +0100 Subject: [PATCH 06/10] Update app on hashchange --- src/App.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index c9205ba..46e5698 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import SingleColumn from './layouts/SingleColumn'; import CreateLinkTile from './components/CreateLinkTile'; @@ -36,9 +36,13 @@ const App: React.FC = () => { ); - if (location.hash) { - if (location.hash.startsWith('#/')) { - page = ; + const [hash, setHash] = useState(location.hash); + + useEffect(() => (window.onhashchange = () => setHash(location.hash)), []); + + if (hash) { + if (hash.startsWith('#/')) { + page = ; } else { page = ( From 1352feca21fb80f792474538befaf4bc2a1cd4ad Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 16 Sep 2020 13:58:14 +0100 Subject: [PATCH 07/10] design nitpicks --- src/App.tsx | 4 ++++ src/components/Avatar.scss | 10 +++++++--- src/components/ClientTile.scss | 6 ++++-- src/components/HomeserverOptions.tsx | 14 ++++++-------- src/components/Input.tsx | 10 +++++----- src/components/InviteTile.scss | 8 +++++++- src/components/InviteTile.tsx | 4 ++-- src/components/RoomPreview.scss | 7 +++---- src/components/RoomPreview.tsx | 6 +++++- src/components/TextButton.scss | 6 +++++- src/components/Toggle.scss | 4 ++++ src/components/UserPreview.scss | 5 ++--- src/components/UserPreview.tsx | 6 +++++- src/pages/LinkRouter.tsx | 12 ++++++++++-- 14 files changed, 69 insertions(+), 33 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 46e5698..164190c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,8 +36,12 @@ const App: React.FC = () => { ); + // Some hacky uri decoding + location.href = decodeURIComponent(location.href); + const [hash, setHash] = useState(location.hash); + console.log(hash); useEffect(() => (window.onhashchange = () => setHash(location.hash)), []); if (hash) { diff --git a/src/components/Avatar.scss b/src/components/Avatar.scss index 7895a82..794064a 100644 --- a/src/components/Avatar.scss +++ b/src/components/Avatar.scss @@ -14,11 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -@import "../color-scheme"; +@import '../color-scheme'; .avatar { border-radius: 100%; border: 1px solid $borders; - height: 50px; - width: 50px; + height: 60px; + width: 60px; +} + +.avatarNoCrop { + border-radius: 0; } diff --git a/src/components/ClientTile.scss b/src/components/ClientTile.scss index 2beb41a..996c56f 100644 --- a/src/components/ClientTile.scss +++ b/src/components/ClientTile.scss @@ -45,12 +45,14 @@ limitations under the License. } p { - margin-right: 20px; + margin-right: 8px; text-align: left; } .button { - width: 50%; + height: 40px; + width: 130px; + margin-top: 16px; } } diff --git a/src/components/HomeserverOptions.tsx b/src/components/HomeserverOptions.tsx index 23a5a6e..ab30a3c 100644 --- a/src/components/HomeserverOptions.tsx +++ b/src/components/HomeserverOptions.tsx @@ -41,7 +41,8 @@ function validateURL(values: FormValues): Partial { try { string().url().parse(values.HSUrl); } catch { - errors.HSUrl = 'This must be a valid url'; + errors.HSUrl = + 'This must be a valid homeserver URL, starting with https://'; } return errors; } @@ -74,7 +75,7 @@ const HomeserverOptions: React.FC = ({ link }: IProps) => { muted={!values.HSUrl} type="text" name="HSUrl" - placeholder="https://example.com" + placeholder="Preferred homeserver URL" /> {values.HSUrl && !errors.HSUrl ? (