matrix-art

An image gallery for Matrix
git clone git://archive.git.mtrnord.blog/MTRNord/matrix-art.git
Log | Files | Refs | README | LICENSE

commit aa9c5c0fdd45c5545fbca0724aa86a61ea1fa4f3
parent f830a917479ddbdf09f94fd7dc5530f10737e9e7
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sat,  1 Jan 2022 20:40:33 +0100

Add basic login

Diffstat:
Mcomponents/Header.tsx | 2+-
Mhelpers/matrix_client.ts | 24+++++++++++++++++++++++-
Mpages/Home.tsx | 4++--
Apages/login.tsx | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 197 insertions(+), 4 deletions(-)

diff --git a/components/Header.tsx b/components/Header.tsx @@ -11,7 +11,7 @@ export default class Header extends Component { <div className='flex grow-[1] h-full relative items-center'> <form className='w-[200px] text-gray-900 dark:text-gray-200'> <div className='flex flex-row box-border items-center cursor-text duration-300 rounded-sm border dark:border-slate-400 border-slate-500 py-1.5 px-2 focus-within:border-teal-400'> - <input className='bg-transparent min-w-[20px] focus:outline-none flex-[1]' type="text" placeholder='Search & Discover' /> + <input className='bg-transparent min-w-[20px] focus:outline-none flex-[1] border-none' type="text" placeholder='Search & Discover' /> <span className='inline-flex items-center justify-center cursor-pointer'> <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" className='fill-gray-900 dark:fill-gray-200'><path d="M0 0h24v24H0z" fill="none" /><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" /></svg> </span> diff --git a/helpers/matrix_client.ts b/helpers/matrix_client.ts @@ -2,7 +2,7 @@ import { MatrixEvent } from './event_types'; import Storage from './storage'; -export const constMatrixArtServer = process.env.NEXT_PUBLIC_DEFAULT_SERVER_URL; +export const constMatrixArtServer = process.env.NEXT_PUBLIC_DEFAULT_SERVER_URL as string; export default class MatrixClient { private joinedRooms: Map<any, any>; @@ -112,6 +112,28 @@ export default class MatrixClient { return data; } + async login(serverUrl: string, username: any, password: any, saveToStorage: any) { + const data = await this.fetchJson(`${serverUrl}/r0/login`, { + method: "POST", + body: JSON.stringify({ + type: "m.login.password", + identifier: { + type: "m.id.user", + user: username, + }, + password: password, + }), + }); + this.serverUrl = serverUrl; + this._userId = data.user_id; + this._accessToken = data.access_token; + this.isGuest = false; + this.serverName = data.home_server; + if (saveToStorage) { + this.saveAuthState(); + } + } + async register(serverUrl: string, username: string, password: string) { const data = await this.fetchJson(`${serverUrl}/r0/register`, { method: "POST", diff --git a/pages/Home.tsx b/pages/Home.tsx @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import { RingLoader } from 'react-spinners'; -import { ImageEvent, ImageGalleryEvent, MatrixEventBase } from '../helpers/event_types'; +import { ImageEvent, ImageGalleryEvent, MatrixEventBase } from '../helpers/event_types'; import Link from 'next/link'; import Head from 'next/head'; import Header from '../components/Header'; @@ -22,7 +22,7 @@ type State = { image_events: ImageEvents[] | []; }; -const centerSpinner = ` +export const centerSpinner = ` position: fixed; top: 50%; left: 50%; diff --git a/pages/login.tsx b/pages/login.tsx @@ -0,0 +1,170 @@ +import Head from "next/head"; +import { NextRouter, withRouter } from "next/router"; +import { Component, ReactNode } from "react"; +import { RingLoader } from "react-spinners"; +import { ClientContext } from "../components/ClientContext"; +import Header from "../components/Header"; +import { constMatrixArtServer } from "../helpers/matrix_client"; +import { centerSpinner } from "./Home"; + +type Props = { + router: NextRouter; +}; +type State = { + showServerField: boolean; + serverUrl: string; + mxid?: string; + password?: string; + serverFieldsDisplay: string; + serverFieldsOpacity: number; + loading: boolean; +}; + +class Login extends Component<Props, State> { + declare context: React.ContextType<typeof ClientContext>; + + constructor(props: Props | Readonly<Props>) { + super(props); + this.state = { + showServerField: false, + serverUrl: constMatrixArtServer + "/_matrix/client", + serverFieldsDisplay: 'none', + serverFieldsOpacity: 0, + loading: false, + } as State; + + this.handleInputChange = this.handleInputChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + handleInputChange(event: { target: any; }) { + const target = event.target; + const value = target.type === 'checkbox' ? target.checked : target.value; + const name = target.name; + if (name == "showServerField") { + if (this.state.serverFieldsDisplay === 'none') { + this.setState({ serverFieldsDisplay: 'block' }); + setTimeout(() => + this.setState({ serverFieldsOpacity: 1 }), 10 // something very short + ); + } + if (this.state.serverFieldsDisplay === 'block') { + this.setState({ serverFieldsOpacity: 0 }); + setTimeout(() => + this.setState({ serverFieldsDisplay: 'none' }), 400 // same as transition time + ); + } + } + this.setState({ + [name]: value + } as State); + } + + async handleSubmit(event: { preventDefault: () => void; }) { + // TODO update client + event.preventDefault(); + let serverUrl = this.state.serverUrl; + // Reset url if the field is hidden. + if (!this.state.showServerField) { + serverUrl = constMatrixArtServer + "/_matrix/client"; + } + + if (this.state.mxid && this.state.password) { + this.setState({ + loading: true + }); + await this.context.client.login(serverUrl, this.state.mxid, this.state.password, true); + this.props.router.replace("/"); + } + } + + render(): ReactNode { + const { loading } = this.state; + if (loading) { + return ( + <> + <div className='h-full bg-[#f8f8f8] dark:bg-[#06070D]'> + <Head> + <title key="title">Matrix Art | Login</title> + <meta property="og:title" content="Matrix Art | Login" key="og-title" /> + <meta property="og:type" content="website" key="og-type" /> + </Head> + <Header></Header> + <main className="lg:pt-[108px] pt-[216px]"> + <div className='z-[100] sticky lg:top-[108px] top-[216px] bg-[#fefefe]/[.95] dark:bg-[#14181E]/[.95]'> + <div className='h-[72px] px-10 w-full relative grid grid-cols-[1fr_auto_1fr] items-center' id='section-grid'> + <h1 className='text-xl text-gray-900 dark:text-gray-200 font-bold'>Log In</h1> + </div> + </div> + // TODO fix loader + </main> + </div> + </> + ); + } + return ( + <> + <div className='h-full bg-[#f8f8f8] dark:bg-[#06070D]'> + <Head> + <title key="title">Matrix Art | Login</title> + <meta property="og:title" content="Matrix Art | Login" key="og-title" /> + <meta property="og:type" content="website" key="og-type" /> + </Head> + <Header></Header> + + <main className='lg:pt-[108px] pt-[216px]'> + <div className='z-[100] sticky lg:top-[108px] top-[216px] bg-[#fefefe]/[.95] dark:bg-[#14181E]/[.95]'> + <div className='h-[72px] px-10 w-full relative grid grid-cols-[1fr_auto_1fr] items-center' id='section-grid'> + <h1 className='text-xl text-gray-900 dark:text-gray-200 font-bold'>Log In</h1> + </div> + </div> + <div className="flex items-center justify-center m-10"> + <form onSubmit={this.handleSubmit} className="grid grid-cols-1 gap-6"> + <div className="block"> + <div className="mt-2"> + <div> + <label className="inline-flex items-center"> + <input className="cursor-pointer h-[16px] w-[16px]" type="checkbox" name="showServerField" checked={this.state.showServerField} onChange={this.handleInputChange} /> + <span className="ml-2 text-gray-900 dark:text-gray-200">Use custom Server</span> + </label> + </div> + </div> + </div> + + <label style={{ + transition: 'opacity 0.4s ease', + opacity: this.state.serverFieldsOpacity, + display: this.state.serverFieldsDisplay + }} id="homeserverField"> + <span className="text-gray-900 dark:text-gray-200">Homeserver:</span> + <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 rounded-sm border dark:border-slate-400 border-slate-500 py-1.5 px-2 focus-within:border-teal-400"> + <input className="bg-transparent min-w-[20px] focus:outline-none flex-[1] border-none text-gray-900 dark:text-gray-200" type="text" name="serverUrl" value={this.state.serverUrl} onChange={this.handleInputChange} /> + </div> + </label> + + <label className="block"> + <span className="text-gray-900 dark:text-gray-200">Username:</span> + <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 rounded-sm border dark:border-slate-400 border-slate-500 py-1.5 px-2 focus-within:border-teal-400"> + <input className="bg-transparent min-w-[20px] focus:outline-none flex-[1] border-none text-gray-900 dark:text-gray-200" type="text" name="mxid" value={this.state.mxid} onChange={this.handleInputChange} /> + </div> + </label> + <label className="block"> + <span className="text-gray-900 dark:text-gray-200">Password:</span> + <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 rounded-sm border dark:border-slate-400 border-slate-500 py-1.5 px-2 focus-within:border-teal-400"> + <input className="bg-transparent min-w-[20px] focus:outline-none flex-[1] border-none text-gray-900 dark:text-gray-200" type="password" name="password" value={this.state.password} onChange={this.handleInputChange} /> + </div> + </label> + + + <input className="bg-teal-400 hover:bg-teal-500 cursor-pointer h-[42px] rounded dark:text-gray-900 text-gray-200" type="submit" value="Log In" /> + </form> + </div> + </main> + </div> + </> + ); + } +} + +Login.contextType = ClientContext; +export default withRouter(Login); +\ No newline at end of file