Dwayne Harris 5 years ago
parent
commit
c4405105f8
  1. 102
      package-lock.json
  2. 3
      package.json
  3. 8
      src/components/app.tsx
  4. 5
      src/components/controls/button.tsx
  5. 2
      src/components/controls/checkbox-field.tsx
  6. 2
      src/components/controls/file-field.tsx
  7. 4
      src/components/create-group-form.tsx
  8. 3
      src/components/create-group-step.tsx
  9. 7
      src/components/create-user-form.tsx
  10. 5
      src/components/create-user-step.tsx
  11. 6
      src/components/group-list-item.tsx
  12. 2
      src/components/horizontal-rule.tsx
  13. 2
      src/components/pages/apps.tsx
  14. 13
      src/components/pages/create-app.tsx
  15. 23
      src/components/pages/developers.tsx
  16. 104
      src/components/pages/edit-app.tsx
  17. 4
      src/components/pages/group-admin.tsx
  18. 3
      src/components/pages/groups.tsx
  19. 13
      src/components/pages/loading.tsx
  20. 15
      src/components/pages/register-group.tsx
  21. 18
      src/components/pages/register.tsx
  22. 26
      src/components/spinner.tsx
  23. 1
      src/images/caret-down.svg
  24. 36
      src/styles/app.css
  25. 47
      src/styles/spinner.css
  26. 6
      src/themes.ts
  27. 8
      webpack.config.ts

102
package-lock.json

@ -213,6 +213,17 @@
"@types/node": "*"
}
},
"@types/copy-webpack-plugin": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/copy-webpack-plugin/-/copy-webpack-plugin-5.0.0.tgz",
"integrity": "sha512-yQHocgdgES7W5Q2UyxJ5cj/E6MrV1zq3MZ8jdApS9NJKqax+rux9IE3QAbBmNCGbgivEsejrkIq3Rm76JLubkg==",
"dev": true,
"requires": {
"@types/minimatch": "*",
"@types/node": "*",
"@types/webpack": "*"
}
},
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
@ -1866,6 +1877,54 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
"dev": true
},
"copy-webpack-plugin": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.0.5.tgz",
"integrity": "sha512-7N68eIoQTyudAuxkfPT7HzGoQ+TsmArN/I3HFwG+lVE3FNzqvZKIiaxtYh4o3BIznioxUvx9j26+Rtsc9htQUQ==",
"dev": true,
"requires": {
"cacache": "^12.0.3",
"find-cache-dir": "^2.1.0",
"glob-parent": "^3.1.0",
"globby": "^7.1.1",
"is-glob": "^4.0.1",
"loader-utils": "^1.2.3",
"minimatch": "^3.0.4",
"normalize-path": "^3.0.0",
"p-limit": "^2.2.1",
"schema-utils": "^1.0.0",
"serialize-javascript": "^2.1.0",
"webpack-log": "^2.0.0"
},
"dependencies": {
"globby": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz",
"integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=",
"dev": true,
"requires": {
"array-union": "^1.0.1",
"dir-glob": "^2.0.0",
"glob": "^7.1.2",
"ignore": "^3.3.5",
"pify": "^3.0.0",
"slash": "^1.0.0"
}
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"serialize-javascript": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.0.tgz",
"integrity": "sha512-a/mxFfU00QT88umAJQsNWOnUKckhNCqOl028N48e7wFmo2/EHpTo9Wso+iJJCMrQnmFvcjto5RJdAHEvVhcyUQ==",
"dev": true
}
}
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@ -2258,6 +2317,15 @@
"randombytes": "^2.0.0"
}
},
"dir-glob": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz",
"integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==",
"dev": true,
"requires": {
"path-type": "^3.0.0"
}
},
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@ -2774,6 +2842,28 @@
"integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
"dev": true
},
"file-loader": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.2.0.tgz",
"integrity": "sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ==",
"dev": true,
"requires": {
"loader-utils": "^1.2.3",
"schema-utils": "^2.0.0"
},
"dependencies": {
"schema-utils": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.5.0.tgz",
"integrity": "sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ==",
"dev": true,
"requires": {
"ajv": "^6.10.2",
"ajv-keywords": "^3.4.1"
}
}
}
},
"filesize": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
@ -4202,6 +4292,12 @@
"integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
"dev": true
},
"ignore": {
"version": "3.3.10",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
"integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
"dev": true
},
"import-cwd": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
@ -7105,6 +7201,12 @@
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"slash": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
"integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
"dev": true
},
"snapdragon": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",

3
package.json

@ -20,6 +20,7 @@
"deploy:prod": "run-s deploy:batch:prod deploy:config:prod"
},
"devDependencies": {
"@types/copy-webpack-plugin": "^5.0.0",
"@types/html-webpack-plugin": "^3.2.1",
"@types/lodash": "^4.14.146",
"@types/mini-css-extract-plugin": "^0.8.0",
@ -33,7 +34,9 @@
"@types/webpack-bundle-analyzer": "^2.13.3",
"@types/webpack-dev-server": "^3.4.0",
"@types/zxcvbn": "^4.4.0",
"copy-webpack-plugin": "^5.0.5",
"css-loader": "^3.2.0",
"file-loader": "^4.2.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.8.0",
"npm-run-all": "^4.1.5",

8
src/components/app.tsx

@ -9,16 +9,16 @@ import { getFetching } from 'src/selectors'
import getConfig from 'src/config'
import { LOCAL_STORAGE_ACCESS_TOKEN_KEY } from 'src/constants'
import { useTheme } from 'src/hooks'
import { useDeepCompareEffect, useTheme } from 'src/hooks'
import { AppState, AppThunkDispatch } from 'src/types'
import Footer from './footer'
import Logo from './logo'
import NavigationMenu from './navigation-menu'
import NotificationContainer from './notification-container'
import Spinner from './spinner'
import Search from './search'
import SelfInfo from './self-info'
import Spinner from './spinner'
import About from './pages/about'
import Apps from './pages/apps'
@ -38,8 +38,6 @@ import ViewPost from './pages/view-post'
import ViewUser from './pages/view-user'
import '../styles/app.css'
import '../styles/spinner.css'
import { useDeepCompareEffect } from 'src/hooks'
const App: FC = () => {
const theme = useTheme()
@ -133,7 +131,7 @@ const App: FC = () => {
<div className="menu" style={{ backgroundColor: theme.backgroundSecondary, borderColor: theme.backgroundPrimary }}>
<Search />
<NavigationMenu />
{fetching && <Spinner color={theme.primary} />}
{fetching && <Spinner />}
<SelfInfo />
<Footer />
</div>

5
src/components/controls/button.tsx

@ -1,10 +1,9 @@
import React, { FC, MouseEventHandler } from 'react'
import noop from 'lodash/noop'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
import { IconDefinition } from '@fortawesome/fontawesome-common-types'
import Spinner from 'src/components/spinner'
export interface Props {
text: string
icon?: IconDefinition
@ -30,7 +29,7 @@ const Button: FC<Props> = ({ text, icon, loading, color, backgroundColor, onClic
return (
<button style={{ backgroundColor, color }} disabled={loading} onClick={onClick}>
{isLoading && <Spinner color={color} />}
{isLoading && <FontAwesomeIcon icon={faSpinner} spin />}
{!isLoading && content()}
</button>
)

2
src/components/controls/checkbox-field.tsx

@ -15,7 +15,7 @@ const CheckboxField: FC<Props> = ({ name, children }) => {
const dispatch = useDispatch()
return (
<label>
<label className="checkbox">
<input type="checkbox" checked={value} onChange={e => dispatch(setFieldValue(name, e.target.checked))} style={{ backgroundColor: theme.primary }} />
&nbsp;&nbsp;
<span style={{ color: theme.text }}>{children}</span>

2
src/components/controls/file-field.tsx

@ -118,7 +118,7 @@ const FileField: FC<Props> = props => {
</div>
}
{!value &&
<div style={{ padding: '1rem 0px' }}>
<div>
<label className="file-input" style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }}>
<input type="file" name={name} onChange={handleChange} />
<div className="icon">

4
src/components/create-group-form.tsx

@ -4,6 +4,7 @@ import { Link } from 'react-router-dom'
import { faIdCard } from '@fortawesome/free-solid-svg-icons'
import { checkGroupAvailability } from 'src/actions/registration'
import { useTheme } from 'src/hooks'
import CheckboxField from 'src/components/controls/checkbox-field'
import TextField from 'src/components/controls/text-field'
@ -14,6 +15,7 @@ import IconImageField from 'src/components/controls/icon-image-field'
import ThemeField from 'src/components/controls/theme-field'
const CreateGroupForm: FC = () => {
const theme = useTheme()
const dispatch = useDispatch()
const checkAvailability = (value: string) => {
@ -37,7 +39,7 @@ const CreateGroupForm: FC = () => {
<CoverImageField name="group-cover-image" />
<IconImageField name="group-icon-image" />
<CheckboxField name="group-agree">
I agree to the Communities <Link to="/terms/communities">terms and conditions</Link>.
I agree to the Communities <Link style={{ color: theme.secondary }} to="/terms/communities">terms and conditions</Link>.
</CheckboxField>
</div>
)

3
src/components/create-group-step.tsx

@ -13,6 +13,7 @@ import { MAX_ID_LENGTH } from 'src/constants'
import { AppState, AppThunkDispatch, NotificationType, Form } from 'src/types'
import CreateGroupForm from './create-group-form'
import HorizontalRule from 'src/components/horizontal-rule'
import PrimaryButton from 'src/components/controls/primary-button'
import SecondaryButton from 'src/components/controls/secondary-button'
@ -53,7 +54,7 @@ const CreateGroupStep: FC<Props> = ({ register }) => {
return (
<div>
<CreateGroupForm />
<hr />
<HorizontalRule />
<nav className="level">
<div>

7
src/components/create-user-form.tsx

@ -4,6 +4,7 @@ import { Link } from 'react-router-dom'
import { faEnvelope, faIdCard, faUserShield } from '@fortawesome/free-solid-svg-icons'
import { checkUserAvailability } from 'src/actions/registration'
import { PRIVACY_OPTIONS } from 'src/constants'
import { useTheme } from 'src/hooks'
import CheckboxField from 'src/components/controls/checkbox-field'
import TextField from 'src/components/controls/text-field'
@ -14,6 +15,7 @@ import CoverImageField from 'src/components/controls/cover-image-field'
import ThemeField from 'src/components/controls/theme-field'
const CreateUserForm: FC = () => {
const theme = useTheme()
const dispatch = useDispatch()
const checkAvailability = (value: string) => {
@ -32,13 +34,14 @@ const CreateUserForm: FC = () => {
<ImageField name="user-image" label="Avatar" />
<CoverImageField name="user-cover-image" />
<SelectField name="user-privacy" label="Privacy" options={PRIVACY_OPTIONS} icon={faUserShield} />
<CheckboxField name="user-requires-approval">
Approve each Subscription request from other users.
</CheckboxField>
<br /><br /><br />
<br />
<CheckboxField name="user-agree">
I agree to the User <Link to="/terms">terms and conditions</Link>.
I agree to the User <Link style={{ color: theme.secondary }} to="/terms">terms and conditions</Link>.
</CheckboxField>
</div>
)

5
src/components/create-user-step.tsx

@ -1,8 +1,6 @@
import React, { FC } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import zxcvbn from 'zxcvbn'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowRight } from '@fortawesome/free-solid-svg-icons'
import { setFieldNotification } from 'src/actions/forms'
import { showNotification } from 'src/actions/notifications'
@ -14,6 +12,7 @@ import { valueFromForm } from 'src/utils'
import { AppState, AppThunkDispatch, NotificationType, Form } from 'src/types'
import CreateUserForm from './create-user-form'
import HorizontalRule from 'src/components/horizontal-rule'
import PrimaryButton from 'src/components/controls/primary-button'
const CreateUserStep: FC = () => {
@ -74,7 +73,7 @@ const CreateUserStep: FC = () => {
return (
<div>
<CreateUserForm />
<hr />
<HorizontalRule />
<nav className="level">
<div>

6
src/components/group-list-item.tsx

@ -18,8 +18,10 @@ const GroupListItem: FC<Props> = ({ group }) => {
<img src={`${config.blobUrl}${group.imageUrl}`} />
</div>
}
<Link to={`/c/${group.id}/register`} style={{ color: theme.primary }}>{group.name}</Link>
{group.about && <p style={{ color: theme.text }}>{group.about}</p>}
<div>
<Link to={`/c/${group.id}/register`} style={{ color: theme.primary }}>{group.name}</Link>
{group.about && <p style={{ color: theme.text }}>{group.about}</p>}
</div>
</div>
)
}

2
src/components/horizontal-rule.tsx

@ -3,7 +3,7 @@ import { useTheme } from 'src/hooks'
const HorizontalRule: FC = () => {
const theme = useTheme()
return <hr style={{ borderColor: theme.backgroundSecondary, color: theme.backgroundSecondary }} />
return <hr style={{ borderColor: theme.secondary, color: theme.secondary }} />
}
export default HorizontalRule

2
src/components/pages/apps.tsx

@ -9,6 +9,7 @@ import { AppState, AppThunkDispatch, App } from 'src/types'
import Title from 'src/components/title'
import Section from 'src/components/section'
import HorizontalRule from 'src/components/horizontal-rule'
import AppListItem from 'src/components/app-list-item'
const Apps: FC = () => {
@ -27,6 +28,7 @@ const Apps: FC = () => {
<Title>Apps</Title>
<p style={{ color: theme.text }}>Use apps to post content to Flexor.</p>
<HorizontalRule />
</Section>
<div style={{ backgroundColor: theme.backgroundSecondary }}>

13
src/components/pages/create-app.tsx

@ -11,6 +11,8 @@ import { setTitle, valueFromForm } from 'src/utils'
import { AppState, Form, NotificationType, AppThunkDispatch, RequestKey } from 'src/types'
import Title from 'src/components/title'
import Section from 'src/components/section'
import HorizontalRule from 'src/components/horizontal-rule'
import PrimaryButton from 'src/components/controls/primary-button'
import TextField from 'src/components/controls/text-field'
import TextareaField from 'src/components/controls/textarea-field'
@ -101,20 +103,21 @@ const CreateApp: FC = () => {
return (
<div>
<Title>Create a new App</Title>
<Section>
<Title>Create a new App</Title>
<HorizontalRule />
<div>
<TextField name="name" label="Name" placeholder="App ID/Name" onBlur={e => checkAvailability(e.target.value)} />
<TextareaField name="about" label="About" placeholder="Description of this app" />
<TextField name="websiteUrl" label="Website" placeholder="Website URL (optional)" />
<TextField name="companyName" label="Company" placeholder="Your company or organization (optional)" />
<TextField name="version" label="Version" placeholder="Current Version of the app (ex: 0.0.1beta5)" />
<hr />
<HorizontalRule />
<ImageField name="image" />
<CoverImageField name="coverImage" />
<IconImageField name="iconImage" />
<hr />
<HorizontalRule />
<TextField name="composerUrl" label="Composer URL" placeholder="URL for the composer web page" />
<TextField name="rendererUrl" label="Renderer URL" placeholder="URL for the renderer template" />
@ -126,7 +129,7 @@ const CreateApp: FC = () => {
<br /><br />
<PrimaryButton text="Create" icon={faCheckCircle} loading={fetching} onClick={() => handleCreate()} />
</div>
</Section>
</div>
)
}

23
src/components/pages/developers.tsx

@ -10,6 +10,8 @@ import { AppState, App, AppThunkDispatch } from 'src/types'
import Title from 'src/components/title'
import Subtitle from 'src/components/subtitle'
import Section from 'src/components/section'
import HorizontalRule from 'src/components/horizontal-rule'
import PrimaryButton from 'src/components/controls/primary-button'
const Developers: FC = () => {
@ -24,18 +26,21 @@ const Developers: FC = () => {
return (
<div>
<Title>Developers</Title>
<Subtitle>Developer Documentation</Subtitle>
<Section>
<Title>Developers</Title>
<Subtitle>Developer Documentation</Subtitle>
<HorizontalRule />
<p>Flexor apps allow users to express themselves on the network.</p>
<br />
<p>Developer documentation coming soon.</p>
<hr />
<p>Flexor apps allow users to express themselves on the network.</p>
<br />
<p>Developer documentation coming soon.</p>
<HorizontalRule />
<p>This is where you manage apps you create.</p>
<br />
<p>This is where you manage apps you create.</p>
<br />
<PrimaryButton text="Create a new App" icon={faPlusCircle} onClick={() => history.push('/developers/create')} />
<PrimaryButton text="Create a new App" icon={faPlusCircle} onClick={() => history.push('/developers/create')} />
</Section>
</div>
)
}

104
src/components/pages/edit-app.tsx

@ -18,8 +18,9 @@ import { setTitle, valueFromForm } from 'src/utils'
import { AppState, AppThunkDispatch, EntityType, App, Form, NotificationType, RequestKey } from 'src/types'
import Title from 'src/components/title'
import Section from 'src/components/section'
import HorizontalRule from 'src/components/horizontal-rule'
import Loading from 'src/components/pages/loading'
import FieldLabel from 'src/components/controls/field-label'
import TextField from 'src/components/controls/text-field'
import TextareaField from 'src/components/controls/textarea-field'
@ -128,64 +129,67 @@ const EditApp: FC = () => {
return (
<div>
<Title>{app.name}</Title>
<div>
<div className="field">
<FieldLabel>Public Key</FieldLabel>
<div className="control-container">
<input className="input" type="text" value={app.publicKey} readOnly />
<span className="icon is-small is-left">
<FontAwesomeIcon icon={faKey} />
</span>
</div>
</div>
<br />
<div className="field">
<p className="control">
<button className="button" onClick={() => setShowPrivateKey(!showPrivateKey)}>
<Section>
<Title>{app.name}</Title>
<HorizontalRule />
<div>
<div className="field">
<FieldLabel>Public Key</FieldLabel>
<div className="control-container">
<input className="input" type="text" value={app.publicKey} readOnly />
<span className="icon is-small is-left">
<FontAwesomeIcon icon={faShieldAlt} />
<FontAwesomeIcon icon={faKey} />
</span>
</button>
</p>
<p className="control is-expanded">
<input className="input" type="text" value={privateKeyDisplay} placeholder="Private Key" readOnly />
</p>
</div>
<hr />
<div className="field">
<FieldLabel>ID</FieldLabel>
<div className="control-container">
<div className="icon">
<FontAwesomeIcon icon={faIdCard} />
</div>
<div className="control">
<input className="input" type="text" value={app.id} readOnly />
</div>
<br />
<div className="field">
<p className="control">
<button className="button" onClick={() => setShowPrivateKey(!showPrivateKey)}>
<span className="icon is-small is-left">
<FontAwesomeIcon icon={faShieldAlt} />
</span>
</button>
</p>
<p className="control is-expanded">
<input className="input" type="text" value={privateKeyDisplay} placeholder="Private Key" readOnly />
</p>
</div>
<HorizontalRule />
<div className="field">
<FieldLabel>ID</FieldLabel>
<div className="control-container">
<div className="icon">
<FontAwesomeIcon icon={faIdCard} />
</div>
<div className="control">
<input className="input" type="text" value={app.id} readOnly />
</div>
</div>
</div>
</div>
<TextField name="name" label="Name" placeholder="App Name" />
<TextareaField name="about" label="About" placeholder="Description of this app" />
<TextField name="websiteUrl" label="Website" placeholder="Website URL (optional)" />
<TextField name="companyName" label="Company" placeholder="Your company or organization (optional)" />
<TextField name="version" label="Version" placeholder="Current Version of the app (ex: 0.0.1beta5)" help={`Last Version: ${app.version}`} />
<hr />
<TextField name="name" label="Name" placeholder="App Name" />
<TextareaField name="about" label="About" placeholder="Description of this app" />
<TextField name="websiteUrl" label="Website" placeholder="Website URL (optional)" />
<TextField name="companyName" label="Company" placeholder="Your company or organization (optional)" />
<TextField name="version" label="Version" placeholder="Current Version of the app (ex: 0.0.1beta5)" help={`Last Version: ${app.version}`} />
<HorizontalRule />
<ImageField name="image" />
<CoverImageField name="coverImage" />
<IconImageField name="iconImage" />
<hr />
<ImageField name="image" />
<CoverImageField name="coverImage" />
<IconImageField name="iconImage" />
<HorizontalRule />
<TextField name="composerUrl" label="Composer URL" placeholder="URL for the composer web page" />
<TextField name="rendererUrl" label="Renderer URL" placeholder="URL for the renderer template" />
<br /><br />
<TextField name="composerUrl" label="Composer URL" placeholder="URL for the composer web page" />
<TextField name="rendererUrl" label="Renderer URL" placeholder="URL for the renderer template" />
<br /><br />
<PrimaryButton text="Save" icon={faCheckCircle} loading={fetching} onClick={() => handleUpdate()} />
</div>
<PrimaryButton text="Save" icon={faCheckCircle} loading={fetching} onClick={() => handleUpdate()} />
</div>
</Section>
</div>
)
}

4
src/components/pages/group-admin.tsx

@ -24,6 +24,7 @@ import {
import Title from 'src/components/title'
import Subtitle from 'src/components/subtitle'
import HorizontalRule from 'src/components/horizontal-rule'
import MemberList from 'src/components/member-list'
import GroupInvitations from 'src/components/group-invitations'
import GroupLogs from 'src/components/group-logs'
@ -102,6 +103,7 @@ const GroupAdmin: FC = () => {
<div>
<Title>{group.name}</Title>
<Subtitle>Administration</Subtitle>
<HorizontalRule />
<div>
<div className="tabs is-large">
@ -157,7 +159,7 @@ const GroupAdmin: FC = () => {
{tab === 'members' &&
<div>
<GroupInvitations group={id} />
<hr />
<HorizontalRule />
<Title>Members</Title>
<MemberList group={id} />

3
src/components/pages/groups.tsx

@ -10,6 +10,7 @@ import { AppState, AppThunkDispatch, Group } from 'src/types'
import Title from 'src/components/title'
import Section from 'src/components/section'
import HorizontalRule from 'src/components/horizontal-rule'
import GroupListItem from 'src/components/group-list-item'
import PrimaryButton from 'src/components/controls/primary-button'
@ -29,7 +30,7 @@ const Groups: FC = () => {
<Title>Communities</Title>
{groups.map(group => <GroupListItem group={group} />)}
<hr />
<HorizontalRule />
<div style={{ textAlign: 'center' }}>
<PrimaryButton text="Create your own Community" icon={faPlusCircle} onClick={() => history.push('/register')} />

13
src/components/pages/loading.tsx

@ -1,14 +1,21 @@
import React, { FC } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
import { useTheme } from 'src/hooks'
import Title from 'src/components/title'
import Spinner from 'src/components/spinner'
import Section from 'src/components/section'
const Loading: FC = () => {
const theme = useTheme()
return (
<div>
<Title>Loading...</Title>
<Spinner color={theme.primary} />
<Section>
<Title>Loading...</Title>
<div style={{ color: theme.secondary, padding: '5rem', textAlign: 'center' }}>
<FontAwesomeIcon icon={faCircleNotch} spin size="4x" />
</div>
</Section>
</div>
)
}

15
src/components/pages/register-group.tsx

@ -18,6 +18,8 @@ import { AppState, AppThunkDispatch, Group, EntityType, NotificationType, Form }
import Title from 'src/components/title'
import Subtitle from 'src/components/subtitle'
import Section from 'src/components/section'
import HorizontalRule from 'src/components/horizontal-rule'
import PrimaryButton from 'src/components/controls/primary-button'
import Loading from 'src/components/pages/loading'
import CreateUserForm from 'src/components/create-user-form'
@ -88,13 +90,16 @@ const RegisterGroup: FC = () => {
return (
<div>
<Title>Register</Title>
<Subtitle>{group.name}</Subtitle>
<Section>
<Title>Register</Title>
<Subtitle>{group.name}</Subtitle>
<HorizontalRule />
<CreateUserForm />
<br />
<CreateUserForm />
<br />
<PrimaryButton text="Create Your Account" icon={faUserPlus} onClick={() => handleRegister()} />
<PrimaryButton text="Create Your Account" icon={faUserPlus} onClick={() => handleRegister()} />
</Section>
</div>
)
}

18
src/components/pages/register.tsx

@ -8,14 +8,15 @@ import { getStep } from 'src/selectors/registration'
import { initForm, initField } from 'src/actions/forms'
import { showNotification } from 'src/actions/notifications'
import { createGroup, register } from 'src/actions/registration'
import { setTitle, valueFromForm } from 'src/utils'
import { AppState, AppThunkDispatch, Form, NotificationType } from 'src/types'
import Title from 'src/components/title'
import Section from 'src/components/section'
import HorizontalRule from 'src/components/horizontal-rule'
import CreateGroupStep from 'src/components/create-group-step'
import CreateUserStep from 'src/components/create-user-step'
import { setTitle, valueFromForm } from 'src/utils'
import { AppState, AppThunkDispatch, Form, NotificationType } from 'src/types'
const Register: FC = () => {
const stepIndex = useSelector<AppState, number>(getStep)
const form = useSelector<AppState, Form>(getForm)
@ -40,7 +41,7 @@ const Register: FC = () => {
name: valueFromForm<string>(form, 'user-name', ''),
imageUrl: valueFromForm<string>(form, 'user-image', ''),
coverImageUrl: valueFromForm<string>(form, 'user-cover-image', ''),
requiresApproval: valueFromForm<boolean>(form, 'user-requires-approval', true),
requiresApproval: valueFromForm<boolean>(form, 'user-requires-approval', false),
privacy: valueFromForm<string>(form, 'user-privacy', 'open'),
theme: valueFromForm<string>(form, 'user-theme', ''),
}))
@ -91,7 +92,7 @@ const Register: FC = () => {
dispatch(initField('password', ''))
dispatch(initField('user-image', '', 'imageUrl'))
dispatch(initField('user-cover-image', '', 'coverImageUrl'))
dispatch(initField('user-requires-approval', true, 'requiresApproval'))
dispatch(initField('user-requires-approval', false, 'requiresApproval'))
dispatch(initField('user-privacy', 'public', 'privacy'))
dispatch(initField('user-theme', '', 'theme'))
dispatch(initField('user-agree', false))
@ -103,9 +104,12 @@ const Register: FC = () => {
return (
<div>
<Title>{title()}</Title>
<Section>
<Title>{title()}</Title>
<HorizontalRule />
{component()}
{component()}
</Section>
</div>
)
}

26
src/components/spinner.tsx

@ -1,21 +1,15 @@
import React, { FC } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
import { useTheme } from 'src/hooks'
interface Props {
color: string
const Spinner: FC = () => {
const theme = useTheme()
return (
<div className="spinner" style={{ color: theme.primary }}>
<FontAwesomeIcon icon={faCircleNotch} spin size="lg" />
</div>
)
}
const Spinner: FC<Props> = ({ color }) => (
<div className="sk-cube-grid">
<div className="sk-cube sk-cube1" style={{ backgroundColor: color }}></div>
<div className="sk-cube sk-cube2" style={{ backgroundColor: color }}></div>
<div className="sk-cube sk-cube3" style={{ backgroundColor: color }}></div>
<div className="sk-cube sk-cube4" style={{ backgroundColor: color }}></div>
<div className="sk-cube sk-cube5" style={{ backgroundColor: color }}></div>
<div className="sk-cube sk-cube6" style={{ backgroundColor: color }}></div>
<div className="sk-cube sk-cube7" style={{ backgroundColor: color }}></div>
<div className="sk-cube sk-cube8" style={{ backgroundColor: color }}></div>
<div className="sk-cube sk-cube9" style={{ backgroundColor: color }}></div>
</div>
)
export default Spinner

1
src/images/caret-down.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"/></svg>

36
src/styles/app.css

@ -5,7 +5,7 @@
:root {
--default-border: 1px solid;
--default-font: 'Source Sans Pro', sans-serif;
--input-padding: 0.5rem;
--input-padding: 0.5rem 0.75rem;
--content-width: 600px;
--menu-width: 270px;
}
@ -31,6 +31,7 @@ body {
input, textarea, select {
border: var(--default-border);
border-radius: 0;
box-sizing: border-box;
font-family: var(--default-font);
font-size: 1rem;
@ -39,10 +40,20 @@ input, textarea, select {
width: 100%;
}
select {
appearance: none;
background: url(../images/caret-down.svg) 98% / 2% no-repeat #ccc;
}
input[type="file"] {
display: none;
}
input[type="checkbox"] {
margin: 0.75rem 0px;
width: initial;
}
h1 {
font-size: 2rem;
margin: 0.5rem 0;
@ -61,6 +72,7 @@ a {
}
hr {
border: 1px solid;
margin: 1rem 0px;
}
@ -87,7 +99,7 @@ iframe {
button, label.file-input {
border: none;
border-radius: 8px;
border-radius: 25px;
cursor: pointer;
font-size: 0.8rem;
font-weight: 700;
@ -102,7 +114,7 @@ div.logo-container {
div.logo {
--size: 40px;
--padding-top: 7px;
--padding-top: 8px;
border-radius: 90px;
font-size: 20px;
@ -119,6 +131,7 @@ div.content {
border-left: var(--default-border);
border-right: var(--default-border);
width: var(--content-width);
padding-bottom: 2rem;
}
div.menu-container {
@ -146,6 +159,11 @@ div.menu > nav > div {
margin-bottom: 0.9rem;
}
div.spinner {
padding: 1rem;
text-align: center;
}
.icon {
display: inline-block;
margin-right: 5px;
@ -158,11 +176,17 @@ footer {
}
div.field {
margin: 1rem 0px;
margin: 2rem 0px;
}
div.field label {
display: block;
font-weight: 700;
margin-bottom: 0.5rem;
}
div.field label.file-input, div.field label.checkbox {
display: inline-block;
}
div.control-container {
@ -175,6 +199,10 @@ div.control-container > div.icon {
padding: var(--input-padding);
}
div.icon > svg {
vertical-align: middle;
}
div.control {
flex-grow: 1;
}

47
src/styles/spinner.css

@ -1,47 +0,0 @@
.sk-cube-grid {
width: 30px;
height: 30px;
margin: 10px auto;
}
.sk-cube-grid .sk-cube {
width: 33%;
height: 33%;
float: left;
animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
}
.sk-cube-grid .sk-cube1 {
animation-delay: 0.2s; }
.sk-cube-grid .sk-cube2 {
animation-delay: 0.3s; }
.sk-cube-grid .sk-cube3 {
animation-delay: 0.4s; }
.sk-cube-grid .sk-cube4 {
animation-delay: 0.1s; }
.sk-cube-grid .sk-cube5 {
animation-delay: 0.2s; }
.sk-cube-grid .sk-cube6 {
animation-delay: 0.3s; }
.sk-cube-grid .sk-cube7 {
animation-delay: 0s; }
.sk-cube-grid .sk-cube8 {
animation-delay: 0.1s; }
.sk-cube-grid .sk-cube9 {
animation-delay: 0.2s; }
@-webkit-keyframes sk-cubeGridScaleDelay {
0%, 70%, 100% {
transform: scale3D(1, 1, 1);
} 35% {
transform: scale3D(0, 0, 1);
}
}
@keyframes sk-cubeGridScaleDelay {
0%, 70%, 100% {
transform: scale3D(1, 1, 1);
} 35% {
transform: scale3D(0, 0, 1);
}
}

6
src/themes.ts

@ -16,7 +16,7 @@ const themes: ThemeCollection = {
[ColorScheme.Dark]: {
primary: '#e5e6fe',
primaryAlternate: '#3b42f4',
secondary: '#fff',
secondary: '#6165e0',
text: '#ddd',
backgroundPrimary: '#000',
backgroundSecondary: '#333',
@ -40,7 +40,7 @@ const themes: ThemeCollection = {
[ColorScheme.Dark]: {
primary: '#ffe6cc',
primaryAlternate: '#ff8000',
secondary: '#fff',
secondary: '#ffb84d',
text: '#ddd',
backgroundPrimary: '#000',
backgroundSecondary: '#333',
@ -64,7 +64,7 @@ const themes: ThemeCollection = {
[ColorScheme.Dark]: {
primary: '#e6ffe6',
primaryAlternate: '#39ac39',
secondary: '#fff',
secondary: '#336600',
text: '#ddd',
backgroundPrimary: '#000',
backgroundSecondary: '#333',

8
webpack.config.ts

@ -1,5 +1,6 @@
import { Configuration } from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import CopyWebpackPlugin from 'copy-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import postcssPresetEnv from 'postcss-preset-env'
import postcssNormalize from 'postcss-normalize'
@ -68,6 +69,10 @@ const config: Configuration = {
}
],
},
{
test: /\.(jpe?g|gif|png|svg)$/,
use: ['file-loader'],
},
],
},
plugins: [
@ -79,6 +84,9 @@ const config: Configuration = {
new MiniCssExtractPlugin({
filename: '[name].css',
}),
// new CopyWebpackPlugin([
// { from: 'src/images/*', to: 'dist/' },
// ]),
// new BundleAnalyzerPlugin(),
],
}

Loading…
Cancel
Save