Add upload icon to file input.

This commit is contained in:
ADAMJR 2023-01-01 16:11:27 +00:00
parent c820f0d77d
commit f45f0fadf1
10 changed files with 187 additions and 35 deletions

View File

@ -13,6 +13,7 @@ import { ready } from '../store/auth';
import { initPings } from '../store/pings';
import VerifyPage from './pages/auth/verify-page';
import InvitePage from './pages/invite-page';
import ThemePage from './pages/theme-page';
export default function App() {
const dispatch = useDispatch();
@ -32,6 +33,7 @@ export default function App() {
<Route exact path="/verify" component={VerifyPage} />
<PrivateRoute exact path="/join/:inviteId" component={InvitePage} />
<PrivateRoute exact path="/themes/:themeCode" component={ThemePage} />
<PrivateRoute exact path="/channels/@me" component={OverviewPage} />
<PrivateRoute exact path="/channels/:guildId/:channelId?" component={GuildPage} />

View File

@ -0,0 +1,23 @@
span.icon {
position: absolute;
top: 65%;
left: 12px;
}
label {
position: relative;
width: 100%;
height: 100%;
}
label:before {
content: "";
position: absolute;
top: 0;
bottom: 0;
width: 20px;
}
input {
padding: 8px 36px;
}

View File

@ -1,21 +1,29 @@
import './file-input.scoped.css';
import { ChangeEventHandler } from 'react';
import Input, { InputProps } from './input';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUpload } from '@fortawesome/free-solid-svg-icons';
type FileInputProps = {
onChange: ChangeEventHandler<HTMLInputElement>;
} & InputProps;
const FileInput: React.FunctionComponent<FileInputProps> = (props) => {
return (
<Input
accept="image/*"
className="pt-5"
label={props.label ?? 'Image'}
register={(): any => {}}
type="file"
{...props}
onChange={props.onChange} />
<label>
<span className="icon">
<FontAwesomeIcon icon={faUpload} />
</span>
<Input
accept="image/*"
className="pt-5"
label={props.label ?? 'Image'}
register={(): any => { }}
type="file"
{...props}
onChange={props.onChange} />
</label>
);
}
export default FileInput;

View File

@ -36,7 +36,7 @@ const GuildSettingsInvites: React.FunctionComponent = () => {
</td>
<td className='w-0'>
<CircleButton
type="button"
role="button"
style={{ borderColor: 'var(--danger)', color: 'var(--danger)' }}
onClick={() => dispatch(deleteInvite(i.id))}>X</CircleButton>
</td>

View File

@ -8,6 +8,7 @@ import SaveChanges from '../../utils/save-changes';
import Input from '../../inputs/input';
import ChannelSelect from '../../inputs/channel-select';
import CircleButton from '../../utils/buttons/circle-button';
import FileInput from '../../inputs/file-input';
const GuildSettingsOverview: React.FunctionComponent = () => {
const dispatch = useDispatch();
@ -39,9 +40,7 @@ const GuildSettingsOverview: React.FunctionComponent = () => {
register={register}
options={{ value: guild.name }}
className="pt-5" />
<Input
type="file"
accept="image/*"
<FileInput
label="Icon Image"
name="iconURL"
className="pt-5"

View File

@ -69,6 +69,15 @@ const UserSettingsThemes: React.FunctionComponent = () => {
handleSubmit(onUpdate)(e);
};
const copyCode = () => {
const inviteURL = `${process.env.REACT_APP_WEBSITE_URL}/themes/${theme?.id}`;
window.navigator.clipboard.writeText(inviteURL);
}
const shortURL = process.env.REACT_APP_WEBSITE_URL
.replace('https://', '')
.replace('http://', '');
const AddTheme: React.FunctionComponent = () => {
const [code, setCode] = useState('');
@ -116,22 +125,23 @@ const UserSettingsThemes: React.FunctionComponent = () => {
<header className="mb-5">
<h1 className="text-3xl font-bold inline">{theme.name}</h1>
</header>
<FileInput
disabled
// disabled={true}!selfIsManager}
className="w-1/3"
name="icon"
label="Icon"
options={{ value: theme.iconURL }}
tooltip="An optional icon for your theme."
onChange={(e) => {
const file = e.currentTarget?.files?.[0];
if (!file) return;
<div className="w-1/3">
<FileInput
disabled
// disabled={true}!selfIsManager}
name="icon"
label="Icon"
options={{ value: theme.iconURL }}
tooltip="An optional icon for your theme."
onChange={(e) => {
const file = e.currentTarget?.files?.[0];
if (!file) return;
dispatch(uploadFile(file, ({ url }) => {
dispatch(updateTheme(themeId, { iconURL: url }));
}));
}} />
dispatch(uploadFile(file, ({ url }) => {
dispatch(updateTheme(themeId, { iconURL: url }));
}));
}} />
</div>
<form
onChange={() => dispatch(openSaveChanges(true))}
@ -144,7 +154,19 @@ const UserSettingsThemes: React.FunctionComponent = () => {
name="name"
register={register}
options={{ value: theme.name }} />
<Input
<div className="mt-8 bg-bg-secondary w-1/2 h-10 rounded-md p-2">
<CircleButton
role="button"
style={{ color: 'var(--font)', borderColor: 'var(--font)' }}
onClick={copyCode}
className="float-right py-0">Copy</CircleButton>
<span className="text-lg">
<span className='muted'>{shortURL + '/join/'}</span>
<span className='primary'>{theme?.code}</span>
</span>
</div>
{/* <Input
disabled
// disabled={!selfIsManager}
tooltip="The code that is used to share themes."
@ -152,7 +174,7 @@ const UserSettingsThemes: React.FunctionComponent = () => {
label="Code"
name="code"
register={register}
options={{ value: theme.code }} />
options={{ value: theme.code }} /> */}
</div>
<div className='mt-2'>

View File

@ -55,7 +55,7 @@ const InvitePage: React.FunctionComponent<InvitePageProps> = () => {
<Wrapper>
<NotFoundIcon />
<h1 className="text-xl font-bold warning">Invite not found...</h1>
<p className="lead">The invite either has expired, or never existed.</p>
<p className="lead">The is not the invite you were looking for.</p>
</Wrapper>
);
@ -87,7 +87,7 @@ const InvitePage: React.FunctionComponent<InvitePageProps> = () => {
history.push(`/channels/${invite.guildId}`);
}}
className="bg-success dark">Join</NormalButton>
<Link to="/">
<Link to="/channels/@me">
<NormalButton className="bg-danger light">Cancel</NormalButton>
</Link>
</div>

View File

@ -0,0 +1,92 @@
import { Entity } from '@acrd/types';
import { faSearchLocation } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useEffect } from 'react';
import { useDispatch, useSelector, useStore } from 'react-redux';
import { Link, useHistory, useParams } from 'react-router-dom';
import fetchEntities from '../../store/actions/fetch-entities';
import { getGuild, getGuildMembers } from '../../store/guilds';
import { joinGuild } from '../../store/members';
import { applyTheme, getThemeByCode, getTheme, unlockTheme } from '../../store/themes';
import { getUser } from '../../store/users';
import SidebarIcon from '../navigation/sidebar/sidebar-icon';
import FoundUsername from '../user/username';
import NormalButton from '../utils/buttons/normal-button';
import FullParticles from '../utils/full-particles';
import PageWrapper from './page-wrapper';
interface ThemePageProps { }
const ThemePage: React.FunctionComponent<ThemePageProps> = () => {
const dispatch = useDispatch();
const { themeCode }: any = useParams();
const theme: Entity.Theme = useSelector(getThemeByCode(themeCode));
const creatorUser: Entity.User = useSelector(getUser(theme?.creatorId));
useEffect(() => {
dispatch(unlockTheme(themeCode));
}, []);
const Wrapper: React.FunctionComponent = ({ children }) => (
<PageWrapper pageTitle={`acrd.app | ${theme?.name} Theme`}>
<div className="flex items-center absolute justify-center h-screen left-[35%]">
<section className="rounded-md shadow bg-bg-primary p-8 w-[478px]">
{children}
</section>
</div>
</PageWrapper>
);
const NotFoundIcon = () => (
<FontAwesomeIcon
className="float-left mr-2"
color="var(--warning)"
icon={faSearchLocation}
size="2x" />
);
if (!theme)
return (
<Wrapper>
<NotFoundIcon />
<h1 className="text-xl font-bold warning">Theme not found...</h1>
<p className="lead">This is not the theme you are looking for.</p>
</Wrapper>
);
return (
<Wrapper>
<FullParticles />
<h1 className="text-3xl font-bold text-center">Unlocked '{theme.name}'!</h1>
<div className="flex mt-5">
<SidebarIcon
name={theme.name}
imageURL={theme.iconURL}
childClasses="bg-bg-tertiary w-24 h-24 pt-6 text-xl"
disableHoverEffect />
<div className="flex justify-around items-center w-full mx-5">
{/* <span className='text-center'>
<div className="heading font-bold text-center">Members</div>
<code>{members.length}</code>
</span> */}
<span className='text-center'>
<div className="heading font-bold">Owned By</div>
<FoundUsername user={creatorUser} />
</span>
</div>
</div>
<div className="flex justify-center gap-5 mx-5 mt-5">
<NormalButton
onClick={() => {
applyTheme(themeCode);
}}
className="bg-success dark">Apply</NormalButton>
<Link to="/channels/@me">
<NormalButton className="bg-danger light">Cancel</NormalButton>
</Link>
</div>
</Wrapper>
);
}
export default ThemePage;

View File

@ -1,6 +1,7 @@
import classNames from 'classnames';
import { HTMLAttributes } from 'react';
const CircleButton: React.FunctionComponent<any> = (props) => {
const CircleButton: React.FunctionComponent<HTMLAttributes<HTMLButtonElement>> = (props) => {
return (
<button
{...props}

View File

@ -1,6 +1,6 @@
/* eslint import/no-webpack-loader-syntax: off */
import { Entity, REST } from '@acrd/types';
import { createSlice } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import { actions as api } from './api';
import { notInArray } from './utils/filter';
import { getHeaders } from './utils/rest-headers';
@ -60,6 +60,11 @@ export const getTheme = (id: string, themes: Entity.Theme[]) => {
return themes.find(t => t.id === id);
}
export const getThemeByCode = (code: string) => createSelector(
state => state.entities.themes,
themes => themes.find(t => t.code === code)
);
export const createTheme = (theme: Partial<Entity.Theme>, callback?: (theme: Entity.Theme) => any) => (dispatch) => {
dispatch(api.restCallBegan({
url: '/themes',