Skip to content

Commit

Permalink
added updating of link
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleksandr Holub committed Dec 1, 2023
1 parent 34ad0c4 commit 9bd0960
Show file tree
Hide file tree
Showing 22 changed files with 260 additions and 168 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Link from '../../../../models/Link.ts';
import {useLinkStore} from '../../../../contexts/AppContext.tsx';
import useAsyncAction from '../../../../hooks/useAsyncAction.ts';
import Button from '../../../Button.tsx';
import Link from '../../../models/Link.ts';
import {useLinkStore} from '../../../contexts/AppContext.tsx';
import useAsyncAction from '../../../hooks/useAsyncAction.ts';
import Button from '../../Button.tsx';
import {useState} from 'react';
import Modal from '../../../Modal.tsx';
import Modal from '../../Modal.tsx';

const DeleteLinkActionButton = ({ id }: Pick<Link, 'id'>) => {
const { deleteLink } = useLinkStore();
Expand All @@ -16,12 +16,10 @@ const DeleteLinkActionButton = ({ id }: Pick<Link, 'id'>) => {
return (
<>
<div className="flex flex-row justify-end gap-6">
<Button type="button" onClick={() => setShowConfirmModal(true)} className="bg-transparent dark:hover:text-zinc-300">
{/*<svg className="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
</svg>
<span className="sr-only">Delete link item</span>*/}
Delete
<Button type="button" onClick={() => setShowConfirmModal(true)} className="bg-transparent">
<svg className="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 20">
<path d="M17 4h-4V2a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v2H1a1 1 0 0 0 0 2h1v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6h1a1 1 0 1 0 0-2ZM7 2h4v2H7V2Zm1 14a1 1 0 1 1-2 0V8a1 1 0 0 1 2 0v8Zm4 0a1 1 0 0 1-2 0V8a1 1 0 0 1 2 0v8Z"/>
</svg>
</Button>
</div>
{ showConfirmModal && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Link from '../../../../models/Link.ts';
import {useLinkStore} from '../../../../contexts/AppContext.tsx';
import useAsyncAction from '../../../../hooks/useAsyncAction.ts';
import Button from '../../../Button.tsx';
import Link from '../../../models/Link.ts';
import {useLinkStore} from '../../../contexts/AppContext.tsx';
import useAsyncAction from '../../../hooks/useAsyncAction.ts';
import Button from '../../Button.tsx';

const shortLikes = (likes: number) => {
if (likes < 1000) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Link from '../../../../models/Link.ts';
import {useLinkStore} from '../../../../contexts/AppContext.tsx';
import useAsyncAction from '../../../../hooks/useAsyncAction.ts';
import Button from '../../../Button.tsx';
import Link from '../../../models/Link.ts';
import {useLinkStore} from '../../../contexts/AppContext.tsx';
import useAsyncAction from '../../../hooks/useAsyncAction.ts';
import Button from '../../Button.tsx';


const SaveLinkActionButton = ({ isSaved, id }: Pick<Link, 'isSaved' | 'id'>) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useState} from 'react';
import TagSearchInput from '../../../components/TagSearchInput.tsx';
import Button from '../../../components/Button.tsx';
import TagSearchInput from './TagSearchInput.tsx';
import Button from '../Button.tsx';

type TagInputProps = {
onAdd: (tag: string) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {YoutubeVideo} from '../../../../models/Link.ts';
import {YoutubeVideo} from '../../../models/Link.ts';

const YoutubeVideoContent = ({ videoId }: YoutubeVideo) => {
return (
Expand Down
52 changes: 43 additions & 9 deletions client/src/components/LinkListItem/LinkListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,54 @@

import LinkListItemContent from './components/LinkListItemContent.tsx';
import LinkListItemTitle from './components/LinkListItemTitle.tsx';
import LinkListItemAuthor from './components/LinkListItemAuthor.tsx';
import LinkListItemTags from './components/LinkListItemTags.tsx';
import LinkListItemActionButtons from './components/LinkListItemActionButtons.tsx';
import LinkListItemContent from './LinkListItemContent.tsx';
import LinkListItemTitle from './LinkListItemTitle.tsx';
import LinkListItemAuthor from './LinkListItemAuthor.tsx';
import LinkListItemTags from './LinkListItemTags.tsx';
import Link from '../../models/Link.ts';
import {useState} from 'react';
import {useLinkStore} from '../../contexts/AppContext.tsx';
import LinkListItemActionPanel from './LinkListItemActionPanel.tsx';
import useAsyncAction from '../../hooks/useAsyncAction.ts';

const LinkListItem = ({ link }: { link: Link }) => {
const [updating, setUpdating] = useState(false);
const { updateLink } = useLinkStore();
const [updateState, setUpdateState] = useState<Pick<Link, 'title' | 'tags'>>({ title: link.title, tags: link.tags });
const {loading, execute} = useAsyncAction(() => updateLink(link.id, updateState));

const handleOnEditClick = () => {
setUpdateState({ title: link.title, tags: link.tags });
setUpdating(true);
};

const handleOnApplyClick = async () => {
await execute();
setUpdating(false);
};

const handleOnRemoveTag = (tag: string) => {
setUpdateState((prevState) => ({ ...prevState, tags: prevState.tags.filter((t) => t !== tag) }));
};

const handleOnAddTag = (tag: string) => {
setUpdateState((prevState) => ({ ...prevState, tags: [...prevState.tags, tag] }));
};

const handleOnTitleUpdate = (title: string) => {
setUpdateState((prevState) => ({ ...prevState, title }));
}

return (
<>
<LinkListItemContent {...link} />
<div className="px-2 mb-4">
<LinkListItemActionButtons {...link} />
<LinkListItemActionPanel
disabled={loading}
updating={updating} link={link}
onApplyClick={handleOnApplyClick}
onCancelClick={() => setUpdating(false)}
onEditClick={handleOnEditClick} />
</div>
<LinkListItemTags tags={link.tags} />
<LinkListItemTitle title={link.title} />
<LinkListItemTags editable={updating} tags={updating ? updateState.tags : link.tags} onRemove={handleOnRemoveTag} onAdd={handleOnAddTag} />
<LinkListItemTitle editable={updating} title={updating ? updateState.title : link.title} onUpdate={handleOnTitleUpdate} />
<LinkListItemAuthor {...link} />
</>
);
Expand Down
52 changes: 52 additions & 0 deletions client/src/components/LinkListItem/LinkListItemActionPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import LikeLinkActionButton from './ActionButtons/LikeLinkActionButton.tsx';
import SaveLinkActionButton from './ActionButtons/SaveLinkActionButton.tsx';
import DeleteLinkActionButton from './ActionButtons/DeleteLinkActionButton.tsx';
import Link from '../../models/Link.ts';
import Button from '../Button.tsx';

type LinkListItemActionPanelProps = {
disabled?: boolean;
onEditClick: () => void;
onCancelClick: () => void;
onApplyClick: () => void;
link: Link;
updating: boolean;
};

const LinkListItemActionPanel = ({ link, disabled, updating, onApplyClick, onCancelClick, onEditClick}: LinkListItemActionPanelProps) => {
return (
<div className="flex flex-row justify-between secondary-text-color">
<div className="flex flex-row gap-4">
<LikeLinkActionButton {...link} />
<SaveLinkActionButton {...link} />
</div>
{ !updating && link.editable && (
<div className="flex flex-row gap-4">
<Button type="button" onClick={onEditClick} className="bg-transparent">
<svg className="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
<path d="M12.687 14.408a3.01 3.01 0 0 1-1.533.821l-3.566.713a3 3 0 0 1-3.53-3.53l.713-3.566a3.01 3.01 0 0 1 .821-1.533L10.905 2H2.167A2.169 2.169 0 0 0 0 4.167v11.666A2.169 2.169 0 0 0 2.167 18h11.666A2.169 2.169 0 0 0 16 15.833V11.1l-3.313 3.308Zm5.53-9.065.546-.546a2.518 2.518 0 0 0 0-3.56 2.576 2.576 0 0 0-3.559 0l-.547.547 3.56 3.56Z"/>
<path d="M13.243 3.2 7.359 9.081a.5.5 0 0 0-.136.256L6.51 12.9a.5.5 0 0 0 .59.59l3.566-.713a.5.5 0 0 0 .255-.136L16.8 6.757 13.243 3.2Z"/>
</svg>
</Button>
<DeleteLinkActionButton {...link} />
</div>
)}
{ updating && (
<div className="flex flex-row gap-4">
<Button disabled={disabled} type="button" onClick={onCancelClick} className="bg-transparent">
<svg className="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 11.793a1 1 0 1 1-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L8.586 10 6.293 7.707a1 1 0 0 1 1.414-1.414L10 8.586l2.293-2.293a1 1 0 0 1 1.414 1.414L11.414 10l2.293 2.293Z"/>
</svg>
</Button>
<Button disabled={disabled} type="button" onClick={onApplyClick} className="bg-transparent">
<svg className="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z"/>
</svg>
</Button>
</div>
)}
</div>
);
};

export default LinkListItemActionPanel;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Link from '../../../models/Link.ts';
import Link from '../../models/Link.ts';

const LinkListItemAuthor = ({ user, createdAt }: Pick<Link, 'user' | 'createdAt'>) => (
<p className="secondary-text-color text-sm italic font-light ">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import YoutubeVideoContent from './Contents/YoutubeVideoContent.tsx';
import Link from '../../../models/Link.ts';
import LinkType from '../../../models/LinkType.ts';
import Link from '../../models/Link.ts';
import LinkType from '../../models/LinkType.ts';

const LinkListItemContent = ({ type, youtube }: Pick<Link, 'type' | 'youtube'>) => {
if (type === LinkType.Youtube) {
Expand Down
39 changes: 39 additions & 0 deletions client/src/components/LinkListItem/LinkListItemTags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {useStore} from '../../contexts/AppContext.tsx';
import LinkStore from '../../stores/LinkStore.ts';
import {observer} from 'mobx-react-lite';
import TagBadge from '../TagBadge.tsx';
import {MaxTags} from '../../constants/preferences.ts';
import AddTagButton from './AddTagButton.tsx';

type LinkListItemTagsProps = {
tags: string[];
editable?: boolean;
error?: string;
onAdd?: (tag: string) => void;
onRemove?: (tag: string) => void;
};

const LinkListItemTags = observer(({ tags, editable, error, onAdd, onRemove }: LinkListItemTagsProps) => {
const { toggleTagFilter } = useStore<LinkStore>(LinkStore);
const handleOnClick = (tag: string) => {
if (!editable) {
toggleTagFilter(tag);
} else if (onRemove) {
onRemove(tag);
}
};

return (
<>
<div className="flex flex-wrap gap-2 items-center">
{tags.map((tag) => (
<TagBadge key={tag} onClick={() => handleOnClick(tag)} name={tag} removable={editable} active />
))}
{ editable && tags.length < MaxTags && <AddTagButton onAdd={(tag) => onAdd && onAdd(tag)} />}
</div>
{ editable && error && (<span className="text-red-500 text-sm">{error}</span>) }
</>
);
});

export default LinkListItemTags;
47 changes: 47 additions & 0 deletions client/src/components/LinkListItem/LinkListItemTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ChangeEvent, useState } from 'react';
import {MaxTitleLength} from '../../constants/preferences.ts';

type LinkListItemTitleProps = {
editable?: boolean;
title: string;
onUpdate?: (title: string) => void;
error?: string;
};

const LinkListItemTitle = ({ title, onUpdate, editable, error }: LinkListItemTitleProps) => {
const [value, setValue] = useState(title);

const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};

const handleEditEnd = () => {
if (value !== title && onUpdate) {
onUpdate(value);
}
};

return (
<>
{ editable && (
<input type="text"
value={value}
onChange={handleInputChange}
onBlur={handleEditEnd}
placeholder="Set title"
className="p-2 group secondary-text-color dark-border w-full bg-zinc-900 focus:outline-none font-medium rounded-lg text-sm"
/>
)}
{ !editable && (
<h5
className="text-lg font-semibold secondary-text-color"
>
{ value.length > MaxTitleLength ? `${value.slice(0, MaxTitleLength)}` : value }
</h5>
)}
{ error && (<span className="text-red-500 text-sm">{error}</span>) }
</>
);
}

export default LinkListItemTitle;
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, {useEffect, useState} from 'react';
import useClickOutsideHandler from '../hooks/useClickOutsideHandler.ts';
import useClickOutsideHandler from '../../hooks/useClickOutsideHandler.ts';
import {observer} from 'mobx-react-lite';
import {useLinkStore} from '../contexts/AppContext.tsx';
import TagBadge from '../components/TagBadge.tsx';
import SearchIcon from '../components/SearchIcon.tsx';
import Button from './Button.tsx';
import {useLinkStore} from '../../contexts/AppContext.tsx';
import TagBadge from '../TagBadge.tsx';
import SearchIcon from '../SearchIcon.tsx';
import Button from '../Button.tsx';

type TagSearchProps = {
showSuggestionsInitial?: boolean;
Expand Down

This file was deleted.

18 changes: 0 additions & 18 deletions client/src/components/LinkListItem/components/LinkListItemTags.tsx

This file was deleted.

This file was deleted.

6 changes: 4 additions & 2 deletions client/src/components/LinkListToolbar/LinkListToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import TopTagsList from './components/TopTagsList.tsx';
import SearchByTitleInput from './components/SearchByTitleInput.tsx';
import TagSearch from './components/TagSearch.tsx';
import UserInteractionsFilter from './components/UserInteractionsFilter.tsx';
import {useLinkStore} from '../../contexts/AppContext.tsx';
import TagSearchInput from '../LinkListItem/TagSearchInput.tsx';

const LinkListToolbar = () => {
const { toggleTagFilter } = useLinkStore();
return (
<div className="flex flex-col gap-4 w-full md:max-w-screen-md">
<TopTagsList />
<div className="flex flex-row gap-2 w-full items-center justify-center">
<TagSearch />
<TagSearchInput onTagClick={toggleTagFilter} />
<UserInteractionsFilter />
</div>
<SearchByTitleInput />
Expand Down

This file was deleted.

Loading

0 comments on commit 9bd0960

Please sign in to comment.