From 1c5a8d026a63211ef4422118a1e84be8a2e05731 Mon Sep 17 00:00:00 2001 From: Ahmed Al-Taiar Date: Fri, 6 Sep 2024 22:08:02 -0400 Subject: [PATCH] Project tags CRUD + fix page metadata --- api/src/functions/graphql.ts | 15 +- api/src/graphql/scalars.sdl.ts | 1 + api/src/graphql/tags.sdl.ts | 6 +- api/src/services/tags/tags.ts | 29 +-- web/package.json | 1 + web/src/Routes.tsx | 7 + .../components/ColorPicker/ColorPicker.tsx | 54 +++++ .../Portrait/PortraitForm/PortraitForm.tsx | 18 +- .../EditProjectCell/EditProjectCell.tsx | 6 +- .../Project/ProjectCell/ProjectCell.tsx | 17 +- .../Project/ProjectsCell/ProjectsCell.tsx | 49 ++--- .../components/Social/NewSocial/NewSocial.tsx | 7 +- .../Social/SocialCell/SocialCell.tsx | 22 +- .../Social/SocialForm/SocialForm.tsx | 8 +- .../Tag/EditTagCell/EditTagCell.tsx | 86 ++++++++ web/src/components/Tag/NewTag/NewTag.tsx | 58 ++++++ web/src/components/Tag/Tag/Tag.tsx | 104 ++++++++++ web/src/components/Tag/TagCell/TagCell.tsx | 34 ++++ web/src/components/Tag/TagForm/TagForm.tsx | 100 +++++++++ web/src/components/Tag/Tags/Tags.tsx | 191 ++++++++++++++++++ web/src/components/Tag/TagsCell/TagsCell.tsx | 31 +++ web/src/index.css | 4 + web/src/layouts/NavbarLayout/NavbarLayout.tsx | 8 + web/src/lib/color.tsx | 12 ++ web/src/pages/ContactPage/ContactPage.tsx | 2 +- web/src/pages/HomePage/HomePage.tsx | 2 +- web/src/pages/LoginPage/LoginPage.tsx | 17 +- web/src/pages/NotFoundPage/NotFoundPage.tsx | 17 +- .../Portrait/PortraitPage/PortraitPage.tsx | 9 +- .../EditProjectPage/EditProjectPage.tsx | 11 +- .../Project/NewProjectPage/NewProjectPage.tsx | 11 +- .../pages/Project/ProjectPage/ProjectPage.tsx | 11 +- .../Project/ProjectsPage/ProjectsPage.tsx | 11 +- .../Social/EditSocialPage/EditSocialPage.tsx | 11 +- .../Social/NewSocialPage/NewSocialPage.tsx | 9 +- .../pages/Social/SocialPage/SocialPage.tsx | 11 +- .../pages/Social/SocialsPage/SocialsPage.tsx | 11 +- web/src/pages/Tag/EditTagPage/EditTagPage.tsx | 16 ++ web/src/pages/Tag/NewTagPage/NewTagPage.tsx | 12 ++ web/src/pages/Tag/TagPage/TagPage.tsx | 16 ++ web/src/pages/Tag/TagsPage/TagsPage.tsx | 12 ++ yarn.lock | 11 + 42 files changed, 933 insertions(+), 135 deletions(-) create mode 100644 web/src/components/ColorPicker/ColorPicker.tsx create mode 100644 web/src/components/Tag/EditTagCell/EditTagCell.tsx create mode 100644 web/src/components/Tag/NewTag/NewTag.tsx create mode 100644 web/src/components/Tag/Tag/Tag.tsx create mode 100644 web/src/components/Tag/TagCell/TagCell.tsx create mode 100644 web/src/components/Tag/TagForm/TagForm.tsx create mode 100644 web/src/components/Tag/Tags/Tags.tsx create mode 100644 web/src/components/Tag/TagsCell/TagsCell.tsx create mode 100644 web/src/lib/color.tsx create mode 100644 web/src/pages/Tag/EditTagPage/EditTagPage.tsx create mode 100644 web/src/pages/Tag/NewTagPage/NewTagPage.tsx create mode 100644 web/src/pages/Tag/TagPage/TagPage.tsx create mode 100644 web/src/pages/Tag/TagsPage/TagsPage.tsx diff --git a/api/src/functions/graphql.ts b/api/src/functions/graphql.ts index 6aeb3e6..c5e182b 100644 --- a/api/src/functions/graphql.ts +++ b/api/src/functions/graphql.ts @@ -1,4 +1,9 @@ -import { URLTypeDefinition, URLResolver } from 'graphql-scalars' +import { + URLTypeDefinition, + URLResolver, + HexColorCodeDefinition, + HexColorCodeResolver, +} from 'graphql-scalars' import { createAuthDecoder } from '@redwoodjs/auth-dbauth-api' import { createGraphQLHandler } from '@redwoodjs/graphql-server' @@ -21,13 +26,11 @@ export const handler = createGraphQLHandler({ sdls, services, schemaOptions: { - typeDefs: [URLTypeDefinition], + typeDefs: [URLTypeDefinition, HexColorCodeDefinition], resolvers: { URL: URLResolver, + HexColorCode: HexColorCodeResolver, }, }, - onException: () => { - // Disconnect from your database with an unhandled exception. - db.$disconnect() - }, + onException: () => db.$disconnect(), }) diff --git a/api/src/graphql/scalars.sdl.ts b/api/src/graphql/scalars.sdl.ts index 6de39f9..84df273 100644 --- a/api/src/graphql/scalars.sdl.ts +++ b/api/src/graphql/scalars.sdl.ts @@ -1,3 +1,4 @@ export const schema = gql` scalar URL + scalar HexColorCode ` diff --git a/api/src/graphql/tags.sdl.ts b/api/src/graphql/tags.sdl.ts index 88e3cff..f4f11fb 100644 --- a/api/src/graphql/tags.sdl.ts +++ b/api/src/graphql/tags.sdl.ts @@ -2,7 +2,7 @@ export const schema = gql` type Tag { id: Int! tag: String! - color: String! + color: HexColorCode! projects: [Project]! } @@ -13,12 +13,12 @@ export const schema = gql` input CreateTagInput { tag: String! - color: String! + color: HexColorCode! } input UpdateTagInput { tag: String - color: String + color: HexColorCode } type Mutation { diff --git a/api/src/services/tags/tags.ts b/api/src/services/tags/tags.ts index d5865f8..e28d2b1 100644 --- a/api/src/services/tags/tags.ts +++ b/api/src/services/tags/tags.ts @@ -6,37 +6,30 @@ import type { import { db } from 'src/lib/db' -export const tags: QueryResolvers['tags'] = () => { - return db.tag.findMany() -} +export const tags: QueryResolvers['tags'] = () => db.tag.findMany() -export const tag: QueryResolvers['tag'] = ({ id }) => { - return db.tag.findUnique({ +export const tag: QueryResolvers['tag'] = ({ id }) => + db.tag.findUnique({ where: { id }, }) -} -export const createTag: MutationResolvers['createTag'] = ({ input }) => { - return db.tag.create({ +export const createTag: MutationResolvers['createTag'] = ({ input }) => + db.tag.create({ data: input, }) -} -export const updateTag: MutationResolvers['updateTag'] = ({ id, input }) => { - return db.tag.update({ +export const updateTag: MutationResolvers['updateTag'] = ({ id, input }) => + db.tag.update({ data: input, where: { id }, }) -} -export const deleteTag: MutationResolvers['deleteTag'] = ({ id }) => { - return db.tag.delete({ +export const deleteTag: MutationResolvers['deleteTag'] = ({ id }) => + db.tag.delete({ where: { id }, }) -} export const Tag: TagRelationResolvers = { - projects: (_obj, { root }) => { - return db.tag.findUnique({ where: { id: root?.id } }).projects() - }, + projects: (_obj, { root }) => + db.tag.findUnique({ where: { id: root?.id } }).projects(), } diff --git a/web/package.json b/web/package.json index 0304e86..fad7d90 100644 --- a/web/package.json +++ b/web/package.json @@ -29,6 +29,7 @@ "@uppy/tus": "^4.0.0", "humanize-string": "2.1.0", "react": "18.3.1", + "react-colorful": "^5.6.1", "react-dom": "18.3.1" }, "devDependencies": { diff --git a/web/src/Routes.tsx b/web/src/Routes.tsx index 48ebbd6..dd2e0ac 100644 --- a/web/src/Routes.tsx +++ b/web/src/Routes.tsx @@ -9,6 +9,13 @@ const Routes = () => { return ( + + + + + + + diff --git a/web/src/components/ColorPicker/ColorPicker.tsx b/web/src/components/ColorPicker/ColorPicker.tsx new file mode 100644 index 0000000..a23606c --- /dev/null +++ b/web/src/components/ColorPicker/ColorPicker.tsx @@ -0,0 +1,54 @@ +import { mdiPound, mdiContentCopy, mdiContentPaste } from '@mdi/js' +import Icon from '@mdi/react' +import { HexColorInput, HexColorPicker } from 'react-colorful' + +import { toast } from '@redwoodjs/web/toast' + +interface ColorPickerProps { + color: string + setColor: React.Dispatch> +} + +const ColorPicker = ({ color, setColor }: ColorPickerProps) => { + return ( +
+
+ +
+
+ {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} + + + +
+
+ ) +} + +export default ColorPicker diff --git a/web/src/components/Portrait/PortraitForm/PortraitForm.tsx b/web/src/components/Portrait/PortraitForm/PortraitForm.tsx index 0a55e20..6f07adb 100644 --- a/web/src/components/Portrait/PortraitForm/PortraitForm.tsx +++ b/web/src/components/Portrait/PortraitForm/PortraitForm.tsx @@ -20,17 +20,15 @@ interface PortraitFormProps { portrait?: Portrait } -export const QUERY: TypedDocumentNode< - FindPortrait, - FindPortraitVariables -> = gql` - query PortraitForm { - portrait { - id - fileId +export const QUERY: TypedDocumentNode = + gql` + query PortraitForm { + portrait { + id + fileId + } } - } -` + ` const DELETE_PORTRAIT_MUTATION: TypedDocumentNode< DeletePortraitMutation, diff --git a/web/src/components/Project/EditProjectCell/EditProjectCell.tsx b/web/src/components/Project/EditProjectCell/EditProjectCell.tsx index 3199e45..da34199 100644 --- a/web/src/components/Project/EditProjectCell/EditProjectCell.tsx +++ b/web/src/components/Project/EditProjectCell/EditProjectCell.tsx @@ -13,6 +13,8 @@ import type { import { useMutation } from '@redwoodjs/web' import { toast } from '@redwoodjs/web/toast' +import CellFailure from 'src/components/Cell/CellFailure/CellFailure' +import CellLoading from 'src/components/Cell/CellLoading/CellLoading' import ProjectForm from 'src/components/Project/ProjectForm' export const QUERY: TypedDocumentNode = gql` @@ -42,10 +44,10 @@ const UPDATE_PROJECT_MUTATION: TypedDocumentNode< } ` -export const Loading = () =>
Loading...
+export const Loading = () => export const Failure = ({ error }: CellFailureProps) => ( -
{error?.message}
+ ) export const Success = ({ project }: CellSuccessProps) => { diff --git a/web/src/components/Project/ProjectCell/ProjectCell.tsx b/web/src/components/Project/ProjectCell/ProjectCell.tsx index 0067c06..ea5ca00 100644 --- a/web/src/components/Project/ProjectCell/ProjectCell.tsx +++ b/web/src/components/Project/ProjectCell/ProjectCell.tsx @@ -6,6 +6,9 @@ import type { TypedDocumentNode, } from '@redwoodjs/web' +import CellEmpty from 'src/components/Cell/CellEmpty/CellEmpty' +import CellFailure from 'src/components/Cell/CellFailure/CellFailure' +import CellLoading from 'src/components/Cell/CellLoading/CellLoading' import Project from 'src/components/Project/Project' export const QUERY: TypedDocumentNode< @@ -23,18 +26,16 @@ export const QUERY: TypedDocumentNode< } ` -export const Loading = () =>
Loading...
+export const Loading = () => -export const Empty = () =>
Project not found
+export const Empty = () => export const Failure = ({ error, -}: CellFailureProps) => ( -
{error?.message}
-) +}: CellFailureProps) => export const Success = ({ project, -}: CellSuccessProps) => { - return -} +}: CellSuccessProps) => ( + +) diff --git a/web/src/components/Project/ProjectsCell/ProjectsCell.tsx b/web/src/components/Project/ProjectsCell/ProjectsCell.tsx index cc5d18e..0546c2b 100644 --- a/web/src/components/Project/ProjectsCell/ProjectsCell.tsx +++ b/web/src/components/Project/ProjectsCell/ProjectsCell.tsx @@ -1,48 +1,39 @@ import type { FindProjects, FindProjectsVariables } from 'types/graphql' -import { Link, routes } from '@redwoodjs/router' import type { CellSuccessProps, CellFailureProps, TypedDocumentNode, } from '@redwoodjs/web' +import CellEmpty from 'src/components/Cell/CellEmpty/CellEmpty' +import CellFailure from 'src/components/Cell/CellFailure/CellFailure' +import CellLoading from 'src/components/Cell/CellLoading/CellLoading' import Projects from 'src/components/Project/Projects' -export const QUERY: TypedDocumentNode< - FindProjects, - FindProjectsVariables -> = gql` - query FindProjects { - projects { - id - title - description - date - links +export const QUERY: TypedDocumentNode = + gql` + query FindProjects { + projects { + id + title + description + date + links + } } - } -` + ` -export const Loading = () =>
Loading...
+export const Loading = () => -export const Empty = () => { - return ( -
- {'No projects yet. '} - - {'Create one?'} - -
- ) -} +export const Empty = () => export const Failure = ({ error }: CellFailureProps) => ( -
{error?.message}
+ ) export const Success = ({ projects, -}: CellSuccessProps) => { - return -} +}: CellSuccessProps) => ( + +) diff --git a/web/src/components/Social/NewSocial/NewSocial.tsx b/web/src/components/Social/NewSocial/NewSocial.tsx index 637b7e6..c698417 100644 --- a/web/src/components/Social/NewSocial/NewSocial.tsx +++ b/web/src/components/Social/NewSocial/NewSocial.tsx @@ -30,15 +30,12 @@ const NewSocial = () => { toast.success('Social created') navigate(routes.socials()) }, - onError: (error) => { - toast.error(error.message) - }, + onError: (error) => toast.error(error.message), } ) - const onSave = (input: CreateSocialInput) => { + const onSave = (input: CreateSocialInput) => createSocial({ variables: { input } }) - } return (
diff --git a/web/src/components/Social/SocialCell/SocialCell.tsx b/web/src/components/Social/SocialCell/SocialCell.tsx index c98f327..3004214 100644 --- a/web/src/components/Social/SocialCell/SocialCell.tsx +++ b/web/src/components/Social/SocialCell/SocialCell.tsx @@ -11,19 +11,17 @@ import CellFailure from 'src/components/Cell/CellFailure/CellFailure' import CellLoading from 'src/components/Cell/CellLoading/CellLoading' import Social from 'src/components/Social/Social' -export const QUERY: TypedDocumentNode< - FindSocialById, - FindSocialByIdVariables -> = gql` - query FindSocialById($id: Int!) { - social: social(id: $id) { - id - name - type - username +export const QUERY: TypedDocumentNode = + gql` + query FindSocialById($id: Int!) { + social: social(id: $id) { + id + name + type + username + } } - } -` + ` export const Loading = () => diff --git a/web/src/components/Social/SocialForm/SocialForm.tsx b/web/src/components/Social/SocialForm/SocialForm.tsx index f653a24..150f2ed 100644 --- a/web/src/components/Social/SocialForm/SocialForm.tsx +++ b/web/src/components/Social/SocialForm/SocialForm.tsx @@ -69,6 +69,9 @@ const SocialForm = (props: SocialFormProps) => { props.onSave(data, props?.social?.id) } + const nameRef = useRef(null) + useEffect(() => nameRef.current?.focus(), []) + return ( onSubmit={onSubmit} @@ -90,6 +93,7 @@ const SocialForm = (props: SocialFormProps) => { { type == 'email' ? mdiAt : type == 'custom' - ? mdiLinkVariant - : mdiAccount + ? mdiLinkVariant + : mdiAccount } /> diff --git a/web/src/components/Tag/EditTagCell/EditTagCell.tsx b/web/src/components/Tag/EditTagCell/EditTagCell.tsx new file mode 100644 index 0000000..615fc29 --- /dev/null +++ b/web/src/components/Tag/EditTagCell/EditTagCell.tsx @@ -0,0 +1,86 @@ +import type { + EditTagById, + UpdateTagInput, + UpdateTagMutationVariables, +} from 'types/graphql' + +import { navigate, routes } from '@redwoodjs/router' +import type { + CellSuccessProps, + CellFailureProps, + TypedDocumentNode, +} from '@redwoodjs/web' +import { useMutation } from '@redwoodjs/web' +import { toast } from '@redwoodjs/web/toast' + +import CellFailure from 'src/components/Cell/CellFailure/CellFailure' +import CellLoading from 'src/components/Cell/CellLoading/CellLoading' +import TagForm from 'src/components/Tag/TagForm' + +export const QUERY: TypedDocumentNode = gql` + query EditTagById($id: Int!) { + tag: tag(id: $id) { + id + tag + color + } + } +` + +const UPDATE_TAG_MUTATION: TypedDocumentNode< + EditTagById, + UpdateTagMutationVariables +> = gql` + mutation UpdateTagMutation($id: Int!, $input: UpdateTagInput!) { + updateTag(id: $id, input: $input) { + id + tag + color + } + } +` + +export const Loading = () => + +export const Failure = ({ error }: CellFailureProps) => ( + +) + +export const Success = ({ tag }: CellSuccessProps) => { + const [updateTag, { loading, error }] = useMutation(UPDATE_TAG_MUTATION, { + onCompleted: () => { + toast.success('Tag updated') + navigate(routes.tags()) + }, + onError: (error) => toast.error(error.message), + }) + + const onSave = (input: UpdateTagInput, id: EditTagById['tag']['id']) => + updateTag({ variables: { id, input } }) + + return ( +
+
+ + + + + + + + + + + +
Edit Tag {tag?.id}
+ +
+
+
+ ) +} diff --git a/web/src/components/Tag/NewTag/NewTag.tsx b/web/src/components/Tag/NewTag/NewTag.tsx new file mode 100644 index 0000000..803ae3e --- /dev/null +++ b/web/src/components/Tag/NewTag/NewTag.tsx @@ -0,0 +1,58 @@ +import type { + CreateTagMutation, + CreateTagInput, + CreateTagMutationVariables, +} from 'types/graphql' + +import { navigate, routes } from '@redwoodjs/router' +import { useMutation } from '@redwoodjs/web' +import type { TypedDocumentNode } from '@redwoodjs/web' +import { toast } from '@redwoodjs/web/toast' + +import TagForm from 'src/components/Tag/TagForm' + +const CREATE_TAG_MUTATION: TypedDocumentNode< + CreateTagMutation, + CreateTagMutationVariables +> = gql` + mutation CreateTagMutation($input: CreateTagInput!) { + createTag(input: $input) { + id + } + } +` + +const NewTag = () => { + const [createTag, { loading, error }] = useMutation(CREATE_TAG_MUTATION, { + onCompleted: () => { + toast.success('Tag created') + navigate(routes.tags()) + }, + onError: (error) => toast.error(error.message), + }) + + const onSave = (input: CreateTagInput) => createTag({ variables: { input } }) + + return ( +
+
+ + + + + + + + + + + +
New Tag
+ +
+
+
+ ) +} + +export default NewTag diff --git a/web/src/components/Tag/Tag/Tag.tsx b/web/src/components/Tag/Tag/Tag.tsx new file mode 100644 index 0000000..f00ede2 --- /dev/null +++ b/web/src/components/Tag/Tag/Tag.tsx @@ -0,0 +1,104 @@ +import type { + DeleteTagMutation, + DeleteTagMutationVariables, + FindTagById, +} from 'types/graphql' + +import { Link, routes, navigate } from '@redwoodjs/router' +import { useMutation } from '@redwoodjs/web' +import type { TypedDocumentNode } from '@redwoodjs/web' +import { toast } from '@redwoodjs/web/toast' + +import {} from 'src/lib/formatters' +import { calculateLuminance } from 'src/lib/color' + +const DELETE_TAG_MUTATION: TypedDocumentNode< + DeleteTagMutation, + DeleteTagMutationVariables +> = gql` + mutation DeleteTagMutation($id: Int!) { + deleteTag(id: $id) { + id + } + } +` + +interface Props { + tag: NonNullable +} + +const Tag = ({ tag }: Props) => { + const [deleteTag] = useMutation(DELETE_TAG_MUTATION, { + onCompleted: () => { + toast.success('Tag deleted') + navigate(routes.tags()) + }, + onError: (error) => toast.error(error.message), + }) + + const onDeleteClick = (id: DeleteTagMutationVariables['id']) => { + if (confirm('Are you sure you want to delete tag ' + id + '?')) + deleteTag({ variables: { id } }) + } + + return ( +
+
+
+ + + + + + + + + + + + + + + + + + + +
+ Tag {tag.id}: {tag.tag} +  
ID{tag.id}
Tag{tag.tag}
Color +
0.5 ? 'black' : 'white', + }} + > + {tag.color} +
+
+
+ +
+
+ ) +} + +export default Tag diff --git a/web/src/components/Tag/TagCell/TagCell.tsx b/web/src/components/Tag/TagCell/TagCell.tsx new file mode 100644 index 0000000..f4ebe3b --- /dev/null +++ b/web/src/components/Tag/TagCell/TagCell.tsx @@ -0,0 +1,34 @@ +import type { FindTagById, FindTagByIdVariables } from 'types/graphql' + +import type { + CellSuccessProps, + CellFailureProps, + TypedDocumentNode, +} from '@redwoodjs/web' + +import CellEmpty from 'src/components/Cell/CellEmpty/CellEmpty' +import CellFailure from 'src/components/Cell/CellFailure/CellFailure' +import CellLoading from 'src/components/Cell/CellLoading/CellLoading' +import Tag from 'src/components/Tag/Tag' + +export const QUERY: TypedDocumentNode = gql` + query FindTagById($id: Int!) { + tag: tag(id: $id) { + id + tag + color + } + } +` + +export const Loading = () => + +export const Empty = () => + +export const Failure = ({ error }: CellFailureProps) => ( + +) + +export const Success = ({ + tag, +}: CellSuccessProps) => diff --git a/web/src/components/Tag/TagForm/TagForm.tsx b/web/src/components/Tag/TagForm/TagForm.tsx new file mode 100644 index 0000000..83ea707 --- /dev/null +++ b/web/src/components/Tag/TagForm/TagForm.tsx @@ -0,0 +1,100 @@ +import { useEffect, useRef, useState } from 'react' + +import { mdiTag } from '@mdi/js' +import Icon from '@mdi/react' +import type { EditTagById, UpdateTagInput } from 'types/graphql' + +import type { RWGqlError } from '@redwoodjs/forms' +import { Form, FieldError, Label, TextField, Submit } from '@redwoodjs/forms' + +import ColorPicker from 'src/components/ColorPicker/ColorPicker' +import { calculateLuminance } from 'src/lib/color' + +type FormTag = NonNullable + +interface TagFormProps { + tag?: EditTagById['tag'] + onSave: (data: UpdateTagInput, id?: FormTag['id']) => void + error: RWGqlError + loading: boolean +} + +const TagForm = (props: TagFormProps) => { + const [tag, setTag] = useState(props.tag?.tag ?? '') + const [color, setColor] = useState(props.tag?.color || '#e5e6e6') + + const onSubmit = (data: FormTag) => { + data.color = color + props.onSave(data, props?.tag?.id) + } + + const tagRef = useRef(null) + useEffect(() => tagRef.current?.focus(), []) + + return ( + + onSubmit={onSubmit} + error={props.error} + className="max-w-56 space-y-2" + > + + + + +
+
0.5 ? 'black' : 'white', + }} + > + {tag} +
+
+ + + + ) +} + +export default TagForm diff --git a/web/src/components/Tag/Tags/Tags.tsx b/web/src/components/Tag/Tags/Tags.tsx new file mode 100644 index 0000000..d4f0ba3 --- /dev/null +++ b/web/src/components/Tag/Tags/Tags.tsx @@ -0,0 +1,191 @@ +import { mdiDotsVertical } from '@mdi/js' +import Icon from '@mdi/react' +import type { + DeleteTagMutation, + DeleteTagMutationVariables, + FindTags, +} from 'types/graphql' + +import { Link, routes } from '@redwoodjs/router' +import { useMutation } from '@redwoodjs/web' +import type { TypedDocumentNode } from '@redwoodjs/web' +import { toast } from '@redwoodjs/web/toast' + +import { QUERY } from 'src/components/Tag/TagsCell' +import { calculateLuminance } from 'src/lib/color' +import { truncate } from 'src/lib/formatters' + +const DELETE_TAG_MUTATION: TypedDocumentNode< + DeleteTagMutation, + DeleteTagMutationVariables +> = gql` + mutation DeleteTagMutation($id: Int!) { + deleteTag(id: $id) { + id + } + } +` + +const TagsList = ({ tags }: FindTags) => { + const [deleteTag] = useMutation(DELETE_TAG_MUTATION, { + onCompleted: () => toast.success('Tag deleted'), + onError: (error) => toast.error(error.message), + refetchQueries: [{ query: QUERY }], + awaitRefetchQueries: true, + }) + + const onDeleteClick = (id: DeleteTagMutationVariables['id']) => { + if (confirm('Are you sure you want to delete tag ' + id + '?')) + deleteTag({ variables: { id } }) + } + + return ( +
+ + + + + + + + + + + {tags.map((tag, i) => { + const actionButtons = ( + <> + + Show + + + Edit + + + + ) + + return ( + + + + + + + ) + })} + +
ColorTagPreview 
+
+
+
+
{truncate(tag.tag)} +
0.5 ? 'black' : 'white', + }} + > + {tag.tag} +
+
+ +
+
+ +
+
+ +
+
+
+
+ ) + // return ( + //
+ // + // + // + // + // + // + // + // + // + // + // {tags.map((tag) => ( + // + // + // + // + // + // + // ))} + // + //
IdTagColor 
{truncate(tag.id)}{truncate(tag.tag)}{truncate(tag.color)} + // + //
+ //
+ // ) +} + +export default TagsList diff --git a/web/src/components/Tag/TagsCell/TagsCell.tsx b/web/src/components/Tag/TagsCell/TagsCell.tsx new file mode 100644 index 0000000..cd72ffc --- /dev/null +++ b/web/src/components/Tag/TagsCell/TagsCell.tsx @@ -0,0 +1,31 @@ +import type { FindTags, FindTagsVariables } from 'types/graphql' + +import type { + CellSuccessProps, + CellFailureProps, + TypedDocumentNode, +} from '@redwoodjs/web' + +import CellEmpty from 'src/components/Cell/CellEmpty/CellEmpty' +import CellFailure from 'src/components/Cell/CellFailure/CellFailure' +import CellLoading from 'src/components/Cell/CellLoading/CellLoading' +import Tags from 'src/components/Tag/Tags' + +export const QUERY: TypedDocumentNode = gql` + query FindTags { + tags { + id + tag + color + } + } +` + +export const Loading = () => +export const Empty = () => +export const Failure = ({ error }: CellFailureProps) => ( + +) +export const Success = ({ + tags, +}: CellSuccessProps) => diff --git a/web/src/index.css b/web/src/index.css index b31cb33..891de2a 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -11,3 +11,7 @@ /** * END --- SETUP TAILWINDCSS EDIT */ + +.w-52 .react-colorful { + width: 13rem; +} diff --git a/web/src/layouts/NavbarLayout/NavbarLayout.tsx b/web/src/layouts/NavbarLayout/NavbarLayout.tsx index 9fa3b0d..9fa58cb 100644 --- a/web/src/layouts/NavbarLayout/NavbarLayout.tsx +++ b/web/src/layouts/NavbarLayout/NavbarLayout.tsx @@ -31,6 +31,14 @@ const NavbarLayout = ({ children }: NavbarLayoutProps) => { name: 'Socials', path: routes.socials(), }, + { + name: 'Projects', + path: routes.projects(), + }, + { + name: 'Tags', + path: routes.tags(), + }, { name: 'Portrait', path: routes.portrait(), diff --git a/web/src/lib/color.tsx b/web/src/lib/color.tsx new file mode 100644 index 0000000..405da30 --- /dev/null +++ b/web/src/lib/color.tsx @@ -0,0 +1,12 @@ +export function calculateLuminance(hex: string) { + const r = parseInt(hex.slice(1, 3), 16) / 255 + const g = parseInt(hex.slice(3, 5), 16) / 255 + const b = parseInt(hex.slice(5, 7), 16) / 255 + + const luminance = + 0.2126 * (r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4)) + + 0.7152 * (g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4)) + + 0.0722 * (b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4)) + + return luminance +} diff --git a/web/src/pages/ContactPage/ContactPage.tsx b/web/src/pages/ContactPage/ContactPage.tsx index f600f33..6e80bf6 100644 --- a/web/src/pages/ContactPage/ContactPage.tsx +++ b/web/src/pages/ContactPage/ContactPage.tsx @@ -30,7 +30,7 @@ const ContactPage = () => { return ( <> - +
diff --git a/web/src/pages/HomePage/HomePage.tsx b/web/src/pages/HomePage/HomePage.tsx index 6c5d30b..4b8605b 100644 --- a/web/src/pages/HomePage/HomePage.tsx +++ b/web/src/pages/HomePage/HomePage.tsx @@ -3,7 +3,7 @@ import { Metadata } from '@redwoodjs/web' const HomePage = () => { return ( <> - + ) } diff --git a/web/src/pages/LoginPage/LoginPage.tsx b/web/src/pages/LoginPage/LoginPage.tsx index 0a96c07..49d8498 100644 --- a/web/src/pages/LoginPage/LoginPage.tsx +++ b/web/src/pages/LoginPage/LoginPage.tsx @@ -24,10 +24,8 @@ const LoginPage = () => { if (isAuthenticated) navigate(routes.home()) }, [isAuthenticated]) - const emailRef = useRef(null) - useEffect(() => { - emailRef.current?.focus() - }, []) + const usernameRef = useRef(null) + useEffect(() => usernameRef.current?.focus(), []) const onSubmit = async (data: Record) => { const response = await logIn({ @@ -53,13 +51,14 @@ const LoginPage = () => { > { > @@ -104,7 +103,7 @@ const LoginPage = () => {
- Log In + Log In
diff --git a/web/src/pages/NotFoundPage/NotFoundPage.tsx b/web/src/pages/NotFoundPage/NotFoundPage.tsx index 0dbc3d2..fbbee67 100644 --- a/web/src/pages/NotFoundPage/NotFoundPage.tsx +++ b/web/src/pages/NotFoundPage/NotFoundPage.tsx @@ -1,12 +1,17 @@ import { Link, routes } from '@redwoodjs/router' +import { Metadata } from '@redwoodjs/web' import ThemeToggle from 'src/components/ThemeToggle/ThemeToggle' export default () => ( -
- - 404 - - -
+ <> + + +
+ + 404 + + +
+ ) diff --git a/web/src/pages/Portrait/PortraitPage/PortraitPage.tsx b/web/src/pages/Portrait/PortraitPage/PortraitPage.tsx index c135a2f..fc4444b 100644 --- a/web/src/pages/Portrait/PortraitPage/PortraitPage.tsx +++ b/web/src/pages/Portrait/PortraitPage/PortraitPage.tsx @@ -1,5 +1,12 @@ +import { Metadata } from '@redwoodjs/web' + import PortraitCell from 'src/components/Portrait/PortraitCell' -const PortraitPage = () => +const PortraitPage = () => ( + <> + + + +) export default PortraitPage diff --git a/web/src/pages/Project/EditProjectPage/EditProjectPage.tsx b/web/src/pages/Project/EditProjectPage/EditProjectPage.tsx index 58e54bd..87e62a2 100644 --- a/web/src/pages/Project/EditProjectPage/EditProjectPage.tsx +++ b/web/src/pages/Project/EditProjectPage/EditProjectPage.tsx @@ -1,11 +1,16 @@ +import { Metadata } from '@redwoodjs/web' + import EditProjectCell from 'src/components/Project/EditProjectCell' type ProjectPageProps = { id: number } -const EditProjectPage = ({ id }: ProjectPageProps) => { - return -} +const EditProjectPage = ({ id }: ProjectPageProps) => ( + <> + + + +) export default EditProjectPage diff --git a/web/src/pages/Project/NewProjectPage/NewProjectPage.tsx b/web/src/pages/Project/NewProjectPage/NewProjectPage.tsx index 10adadc..aa9ca0a 100644 --- a/web/src/pages/Project/NewProjectPage/NewProjectPage.tsx +++ b/web/src/pages/Project/NewProjectPage/NewProjectPage.tsx @@ -1,7 +1,12 @@ +import { Metadata } from '@redwoodjs/web' + import NewProject from 'src/components/Project/NewProject' -const NewProjectPage = () => { - return -} +const NewProjectPage = () => ( + <> + + + +) export default NewProjectPage diff --git a/web/src/pages/Project/ProjectPage/ProjectPage.tsx b/web/src/pages/Project/ProjectPage/ProjectPage.tsx index eff2567..e5ebe22 100644 --- a/web/src/pages/Project/ProjectPage/ProjectPage.tsx +++ b/web/src/pages/Project/ProjectPage/ProjectPage.tsx @@ -1,11 +1,16 @@ +import { Metadata } from '@redwoodjs/web' + import ProjectCell from 'src/components/Project/ProjectCell' type ProjectPageProps = { id: number } -const ProjectPage = ({ id }: ProjectPageProps) => { - return -} +const ProjectPage = ({ id }: ProjectPageProps) => ( + <> + + + +) export default ProjectPage diff --git a/web/src/pages/Project/ProjectsPage/ProjectsPage.tsx b/web/src/pages/Project/ProjectsPage/ProjectsPage.tsx index 435c1e2..ddc861a 100644 --- a/web/src/pages/Project/ProjectsPage/ProjectsPage.tsx +++ b/web/src/pages/Project/ProjectsPage/ProjectsPage.tsx @@ -1,7 +1,12 @@ +import { Metadata } from '@redwoodjs/web' + import ProjectsCell from 'src/components/Project/ProjectsCell' -const ProjectsPage = () => { - return -} +const ProjectsPage = () => ( + <> + + + +) export default ProjectsPage diff --git a/web/src/pages/Social/EditSocialPage/EditSocialPage.tsx b/web/src/pages/Social/EditSocialPage/EditSocialPage.tsx index f2c34a4..d2f3806 100644 --- a/web/src/pages/Social/EditSocialPage/EditSocialPage.tsx +++ b/web/src/pages/Social/EditSocialPage/EditSocialPage.tsx @@ -1,11 +1,16 @@ +import { Metadata } from '@redwoodjs/web' + import EditSocialCell from 'src/components/Social/EditSocialCell' type SocialPageProps = { id: number } -const EditSocialPage = ({ id }: SocialPageProps) => { - return -} +const EditSocialPage = ({ id }: SocialPageProps) => ( + <> + + + +) export default EditSocialPage diff --git a/web/src/pages/Social/NewSocialPage/NewSocialPage.tsx b/web/src/pages/Social/NewSocialPage/NewSocialPage.tsx index da1bf0a..a200a81 100644 --- a/web/src/pages/Social/NewSocialPage/NewSocialPage.tsx +++ b/web/src/pages/Social/NewSocialPage/NewSocialPage.tsx @@ -1,5 +1,12 @@ +import { Metadata } from '@redwoodjs/web' + import NewSocial from 'src/components/Social/NewSocial/NewSocial' -const NewSocialPage = () => +const NewSocialPage = () => ( + <> + + + +) export default NewSocialPage diff --git a/web/src/pages/Social/SocialPage/SocialPage.tsx b/web/src/pages/Social/SocialPage/SocialPage.tsx index aa3494c..d02e627 100644 --- a/web/src/pages/Social/SocialPage/SocialPage.tsx +++ b/web/src/pages/Social/SocialPage/SocialPage.tsx @@ -1,11 +1,16 @@ +import { Metadata } from '@redwoodjs/web' + import SocialCell from 'src/components/Social/SocialCell' type SocialPageProps = { id: number } -const SocialPage = ({ id }: SocialPageProps) => { - return -} +const SocialPage = ({ id }: SocialPageProps) => ( + <> + + + +) export default SocialPage diff --git a/web/src/pages/Social/SocialsPage/SocialsPage.tsx b/web/src/pages/Social/SocialsPage/SocialsPage.tsx index 0bc3c91..287facb 100644 --- a/web/src/pages/Social/SocialsPage/SocialsPage.tsx +++ b/web/src/pages/Social/SocialsPage/SocialsPage.tsx @@ -1,7 +1,12 @@ +import { Metadata } from '@redwoodjs/web' + import SocialsCell from 'src/components/Social/SocialsCell' -const SocialsPage = () => { - return -} +const SocialsPage = () => ( + <> + + + +) export default SocialsPage diff --git a/web/src/pages/Tag/EditTagPage/EditTagPage.tsx b/web/src/pages/Tag/EditTagPage/EditTagPage.tsx new file mode 100644 index 0000000..0b3ac9e --- /dev/null +++ b/web/src/pages/Tag/EditTagPage/EditTagPage.tsx @@ -0,0 +1,16 @@ +import { Metadata } from '@redwoodjs/web' + +import EditTagCell from 'src/components/Tag/EditTagCell' + +type TagPageProps = { + id: number +} + +const EditTagPage = ({ id }: TagPageProps) => ( + <> + + + +) + +export default EditTagPage diff --git a/web/src/pages/Tag/NewTagPage/NewTagPage.tsx b/web/src/pages/Tag/NewTagPage/NewTagPage.tsx new file mode 100644 index 0000000..536176a --- /dev/null +++ b/web/src/pages/Tag/NewTagPage/NewTagPage.tsx @@ -0,0 +1,12 @@ +import { Metadata } from '@redwoodjs/web' + +import NewTag from 'src/components/Tag/NewTag' + +const NewTagPage = () => ( + <> + + + +) + +export default NewTagPage diff --git a/web/src/pages/Tag/TagPage/TagPage.tsx b/web/src/pages/Tag/TagPage/TagPage.tsx new file mode 100644 index 0000000..58880e5 --- /dev/null +++ b/web/src/pages/Tag/TagPage/TagPage.tsx @@ -0,0 +1,16 @@ +import { Metadata } from '@redwoodjs/web' + +import TagCell from 'src/components/Tag/TagCell' + +type TagPageProps = { + id: number +} + +const TagPage = ({ id }: TagPageProps) => ( + <> + + + +) + +export default TagPage diff --git a/web/src/pages/Tag/TagsPage/TagsPage.tsx b/web/src/pages/Tag/TagsPage/TagsPage.tsx new file mode 100644 index 0000000..84b1731 --- /dev/null +++ b/web/src/pages/Tag/TagsPage/TagsPage.tsx @@ -0,0 +1,12 @@ +import { Metadata } from '@redwoodjs/web' + +import TagsCell from 'src/components/Tag/TagsCell' + +const TagsPage = () => ( + <> + + + +) + +export default TagsPage diff --git a/yarn.lock b/yarn.lock index fd57ed1..90f5652 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15548,6 +15548,16 @@ __metadata: languageName: node linkType: hard +"react-colorful@npm:^5.6.1": + version: 5.6.1 + resolution: "react-colorful@npm:5.6.1" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/48eb73cf71e10841c2a61b6b06ab81da9fffa9876134c239bfdebcf348ce2a47e56b146338e35dfb03512c85966bfc9a53844fc56bc50154e71f8daee59ff6f0 + languageName: node + linkType: hard + "react-dom@npm:18.3.1": version: 18.3.1 resolution: "react-dom@npm:18.3.1" @@ -18237,6 +18247,7 @@ __metadata: postcss: "npm:^8.4.41" postcss-loader: "npm:^8.1.1" react: "npm:18.3.1" + react-colorful: "npm:^5.6.1" react-dom: "npm:18.3.1" tailwindcss: "npm:^3.4.8" languageName: unknown