6 Commits

17 changed files with 111 additions and 50 deletions

View File

@ -1,5 +1,5 @@
[general] [general]
host = '0.0.0.0:3333' hostname = '0.0.0.0:3333'
[email_notifications] [email_notifications]
enabled = true enabled = true

View File

@ -6,14 +6,6 @@
"@apollo/client": "^3.0.0-rc.8", "@apollo/client": "^3.0.0-rc.8",
"@apollo/react-common": "^3.1.4", "@apollo/react-common": "^3.1.4",
"@apollo/react-hooks": "^3.1.3", "@apollo/react-hooks": "^3.1.3",
"@fortawesome/fontawesome-svg-core": "^1.2.27",
"@fortawesome/free-brands-svg-icons": "^5.12.1",
"@fortawesome/free-regular-svg-icons": "^5.12.1",
"@fortawesome/free-solid-svg-icons": "^5.12.1",
"@fortawesome/react-fontawesome": "^0.1.8",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"@types/color": "^3.0.1", "@types/color": "^3.0.1",
"@types/date-fns": "^2.6.0", "@types/date-fns": "^2.6.0",

View File

@ -59,7 +59,7 @@ const Install = () => {
} else { } else {
const response: RefreshTokenResponse = await x.data; const response: RefreshTokenResponse = await x.data;
const { accessToken: newToken, isInstalled } = response; const { accessToken: newToken, isInstalled } = response;
const claims: JWTToken = jwtDecode(accessToken); const claims: JWTToken = jwtDecode(newToken);
const currentUser = { const currentUser = {
id: claims.userId, id: claims.userId,
roles: { roles: {
@ -69,7 +69,7 @@ const Install = () => {
}, },
}; };
setUser(currentUser); setUser(currentUser);
setAccessToken(accessToken); setAccessToken(newToken);
if (!isInstalled) { if (!isInstalled) {
history.replace('/install'); history.replace('/install');
} }

View File

@ -8,6 +8,8 @@ import {
useCreateProjectMutation, useCreateProjectMutation,
GetProjectsDocument, GetProjectsDocument,
GetProjectsQuery, GetProjectsQuery,
MeQuery,
MeDocument,
} from 'shared/generated/graphql'; } from 'shared/generated/graphql';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';

View File

@ -1,9 +1,7 @@
import styled, { css, keyframes } from 'styled-components'; import styled, { css, keyframes } from 'styled-components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
import TextareaAutosize from 'react-autosize-textarea'; import TextareaAutosize from 'react-autosize-textarea';
import { CheckCircle, CheckSquareOutline } from 'shared/icons'; import { CheckCircle, CheckSquareOutline, Clock } from 'shared/icons';
import { RefObject } from 'react';
import TaskAssignee from 'shared/components/TaskAssignee'; import TaskAssignee from 'shared/components/TaskAssignee';
export const CardMember = styled(TaskAssignee)<{ zIndex: number }>` export const CardMember = styled(TaskAssignee)<{ zIndex: number }>`
@ -20,7 +18,9 @@ export const ChecklistIcon = styled(CheckSquareOutline)<{ color: 'success' | 'no
stroke: rgba(${props.theme.colors.success}); stroke: rgba(${props.theme.colors.success});
`} `}
`; `;
export const ClockIcon = styled(FontAwesomeIcon)``; export const ClockIcon = styled(Clock)<{ color: string }>`
fill: ${props => props.color};
`;
export const EditorTextarea = styled(TextareaAutosize)` export const EditorTextarea = styled(TextareaAutosize)`
overflow: hidden; overflow: hidden;

View File

@ -1,7 +1,5 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Pencil, Eye, List } from 'shared/icons';
import { faPencilAlt, faList } from '@fortawesome/free-solid-svg-icons';
import { faClock, faEye } from '@fortawesome/free-regular-svg-icons';
import { import {
EditorTextarea, EditorTextarea,
CardMember, CardMember,
@ -155,7 +153,7 @@ const Card = React.forwardRef(
} }
}} }}
> >
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} /> <Pencil width={8} height={8} />
</ListCardOperation> </ListCardOperation>
)} )}
<ListCardDetails complete={complete ?? false}> <ListCardDetails complete={complete ?? false}>
@ -218,18 +216,18 @@ const Card = React.forwardRef(
<ListCardBadges> <ListCardBadges>
{watched && ( {watched && (
<ListCardBadge> <ListCardBadge>
<FontAwesomeIcon color="#6b778c" icon={faEye} size="xs" /> <Eye width={8} height={8} />
</ListCardBadge> </ListCardBadge>
)} )}
{dueDate && ( {dueDate && (
<DueDateCardBadge isPastDue={dueDate.isPastDue}> <DueDateCardBadge isPastDue={dueDate.isPastDue}>
<ClockIcon color={dueDate.isPastDue ? '#fff' : '#6b778c'} icon={faClock} size="xs" /> <ClockIcon color={dueDate.isPastDue ? '#fff' : '#6b778c'} width={8} height={8} />
<ListCardBadgeText>{dueDate.formattedDate}</ListCardBadgeText> <ListCardBadgeText>{dueDate.formattedDate}</ListCardBadgeText>
</DueDateCardBadge> </DueDateCardBadge>
)} )}
{description && ( {description && (
<DescriptionBadge> <DescriptionBadge>
<FontAwesomeIcon color="#6b778c" icon={faList} size="xs" /> <List width={8} height={8} />
</DescriptionBadge> </DescriptionBadge>
)} )}
{checklists && ( {checklists && (

View File

@ -1,15 +1,15 @@
import styled from 'styled-components'; import styled from 'styled-components';
import Button from 'shared/components/Button'; import Button from 'shared/components/Button';
import TextareaAutosize from 'react-autosize-textarea'; import TextareaAutosize from 'react-autosize-textarea';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
export const CancelIcon = styled(FontAwesomeIcon)` export const CancelIconWrapper = styled.div`
opacity: 0.8; opacity: 0.8;
cursor: pointer; cursor: pointer;
font-size: 1.25em; font-size: 1.25em;
padding-left: 5px; padding-left: 5px;
`; `;
export const CardComposerWrapper = styled.div<{ isOpen: boolean }>` export const CardComposerWrapper = styled.div<{ isOpen: boolean }>`
padding-bottom: 8px; padding-bottom: 8px;
display: ${props => (props.isOpen ? 'flex' : 'none')}; display: ${props => (props.isOpen ? 'flex' : 'none')};

View File

@ -1,12 +1,12 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown'; import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import useOnOutsideClick from 'shared/hooks/onOutsideClick'; import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import { Cross } from 'shared/icons';
import { import {
CardComposerWrapper, CardComposerWrapper,
CancelIcon, CancelIconWrapper,
AddCardButton, AddCardButton,
ComposerControls, ComposerControls,
ComposerControlsSaveSection, ComposerControlsSaveSection,
@ -52,7 +52,9 @@ const CardComposer = ({ isOpen, onCreateCard, onClose }: Props) => {
> >
Add Card Add Card
</AddCardButton> </AddCardButton>
<CancelIcon onClick={onClose} icon={faTimes} color="#42526e" /> <CancelIconWrapper onClick={() => onClose()}>
<Cross width={12} height={12} />
</CancelIconWrapper>
</ComposerControlsSaveSection> </ComposerControlsSaveSection>
<ComposerControlsActionsSection /> <ComposerControlsActionsSection />
</ComposerControls> </ComposerControls>

View File

@ -585,3 +585,30 @@ export const ActivityItemLog = styled.span`
margin-left: 2px; margin-left: 2px;
color: rgba(${props => props.theme.colors.text.primary}); color: rgba(${props => props.theme.colors.text.primary});
`; `;
export const ViewRawButton = styled.button`
border-radius: 3px;
padding: 8px 12px;
display: flex;
position: absolute;
right: 4px;
bottom: -24px;
cursor: pointer;
color: rgba(${props => props.theme.colors.text.primary}, 0.25);
&:hover {
color: rgba(${props => props.theme.colors.text.primary});
}
`;
export const TaskDetailsEditor = styled(TextareaAutosize)`
min-height: 108px;
color: #c2c6dc;
background: #262c49;
border-radius: 3px;
line-height: 20px;
margin-left: 32px;
margin-right: 32px;
padding: 9px 8px 7px 8px;
outline: none;
border: none;
`;

View File

@ -30,6 +30,7 @@ import {
AssignUserLabel, AssignUserLabel,
AssignUsersButton, AssignUsersButton,
AssignedUsersSection, AssignedUsersSection,
ViewRawButton,
DueDateTitle, DueDateTitle,
Container, Container,
LeftSidebar, LeftSidebar,
@ -65,6 +66,7 @@ import {
CommentProfile, CommentProfile,
CommentInnerWrapper, CommentInnerWrapper,
ActivitySection, ActivitySection,
TaskDetailsEditor,
} from './Styles'; } from './Styles';
import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist'; import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist';
import onDragEnd from './onDragEnd'; import onDragEnd from './onDragEnd';
@ -153,6 +155,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
return true; return true;
}); });
const [saveTimeout, setSaveTimeout] = useState<any>(null); const [saveTimeout, setSaveTimeout] = useState<any>(null);
const [showRaw, setShowRaw] = useState(false);
const [showCommentActions, setShowCommentActions] = useState(false); const [showCommentActions, setShowCommentActions] = useState(false);
const taskDescriptionRef = useRef(task.description ?? ''); const taskDescriptionRef = useRef(task.description ?? '');
const $noMemberBtn = useRef<HTMLDivElement>(null); const $noMemberBtn = useRef<HTMLDivElement>(null);
@ -309,28 +312,34 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
</HeaderContainer> </HeaderContainer>
<InnerContentContainer> <InnerContentContainer>
<DescriptionContainer> <DescriptionContainer>
<EditorContainer {showRaw ? (
onClick={e => { <TaskDetailsEditor value={taskDescriptionRef.current} />
if (!editTaskDescription) { ) : (
setEditTaskDescription(true); <EditorContainer
} onClick={e => {
}} if (!editTaskDescription) {
> setEditTaskDescription(true);
<Editor }
defaultValue={task.description ?? ''}
theme={dark}
readOnly={!editTaskDescription}
autoFocus
onChange={value => {
setSaveTimeout(() => {
clearTimeout(saveTimeout);
return setTimeout(saveDescription, 2000);
});
const text = value();
taskDescriptionRef.current = text;
}} }}
/> >
</EditorContainer> <Editor
defaultValue={task.description ?? ''}
theme={dark}
readOnly={!editTaskDescription}
autoFocus
onChange={value => {
setSaveTimeout(() => {
clearTimeout(saveTimeout);
return setTimeout(saveDescription, 2000);
});
const text = value();
taskDescriptionRef.current = text;
}}
/>
</EditorContainer>
)}
<ViewRawButton onClick={() => setShowRaw(!showRaw)}>{showRaw ? 'Show editor' : 'Show raw'}</ViewRawButton>
</DescriptionContainer> </DescriptionContainer>
<ChecklistSection> <ChecklistSection>
<DragDropContext onDragEnd={result => onDragEnd(result, task, onChecklistDrop, onChecklistItemDrop)}> <DragDropContext onDragEnd={result => onDragEnd(result, task, onChecklistDrop, onChecklistItemDrop)}>

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const Eye: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 576 512">
<path d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z" />
</Icon>
);
};
export default Eye;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const List: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
<path d="M80 368H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm0-320H16A16 16 0 0 0 0 64v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16zm0 160H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm416 176H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z" />
</Icon>
);
};
export default List;

View File

@ -1,5 +1,7 @@
import Cross from './Cross'; import Cross from './Cross';
import Cog from './Cog'; import Cog from './Cog';
import Eye from './Eye';
import List from './List';
import At from './At'; import At from './At';
import Task from './Task'; import Task from './Task';
import Smile from './Smile'; import Smile from './Smile';
@ -85,4 +87,6 @@ export {
Clone, Clone,
Paperclip, Paperclip,
Share, Share,
Eye,
List,
}; };

View File

@ -1,6 +1,7 @@
let accessToken = ''; let accessToken = '';
export function setAccessToken(newToken: string) { export function setAccessToken(newToken: string) {
console.log(newToken);
accessToken = newToken; accessToken = newToken;
} }
export function getAccessToken() { export function getAccessToken() {

View File

@ -251,10 +251,12 @@ func (h *TaskcafeHandler) InstallHandler(w http.ResponseWriter, r *http.Request)
refreshExpiresAt := refreshCreatedAt.AddDate(0, 0, 1) refreshExpiresAt := refreshCreatedAt.AddDate(0, 0, 1)
refreshTokenString, err := h.repo.CreateRefreshToken(r.Context(), db.CreateRefreshTokenParams{user.UserID, refreshCreatedAt, refreshExpiresAt}) refreshTokenString, err := h.repo.CreateRefreshToken(r.Context(), db.CreateRefreshTokenParams{user.UserID, refreshCreatedAt, refreshExpiresAt})
log.WithField("userID", user.UserID.String()).Info("creating install access token")
accessTokenString, err := auth.NewAccessToken(user.UserID.String(), auth.Unrestricted, user.RoleCode) accessTokenString, err := auth.NewAccessToken(user.UserID.String(), auth.Unrestricted, user.RoleCode)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
} }
log.Info(accessTokenString)
w.Header().Set("Content-type", "application/json") w.Header().Set("Content-type", "application/json")
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{

View File

@ -61,9 +61,9 @@ func (h *TaskcafeHandler) ProfileImageUpload(w http.ResponseWriter, r *http.Requ
return return
} }
h.repo.UpdateUserAccountProfileAvatarURL(r.Context(), db.UpdateUserAccountProfileAvatarURLParams{UserID: userID, ProfileAvatarUrl: sql.NullString{String: "http://localhost:3333/uploads/" + handler.Filename, Valid: true}}) h.repo.UpdateUserAccountProfileAvatarURL(r.Context(), db.UpdateUserAccountProfileAvatarURLParams{UserID: userID, ProfileAvatarUrl: sql.NullString{String: "/uploads/" + handler.Filename, Valid: true}})
// return that we have successfully uploaded our file! // return that we have successfully uploaded our file!
log.Info("file uploaded") log.Info("file uploaded")
json.NewEncoder(w).Encode(AvatarUploadResponseData{URL: "http://localhost:3333/uploads/" + handler.Filename, UserID: userID.String()}) json.NewEncoder(w).Encode(AvatarUploadResponseData{URL: "/uploads/" + handler.Filename, UserID: userID.String()})
} }