Project tags CRUD + fix page metadata
This commit is contained in:
@ -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(),
|
||||
})
|
||||
|
@ -1,3 +1,4 @@
|
||||
export const schema = gql`
|
||||
scalar URL
|
||||
scalar HexColorCode
|
||||
`
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
}
|
||||
|
@ -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": {
|
||||
|
@ -9,6 +9,13 @@ const Routes = () => {
|
||||
return (
|
||||
<Router useAuth={useAuth}>
|
||||
<PrivateSet unauthenticated="home">
|
||||
<Set wrap={ScaffoldLayout} title="Tags" titleTo="tags" buttonLabel="New Tag" buttonTo="newTag">
|
||||
<Route path="/admin/tags/new" page={TagNewTagPage} name="newTag" />
|
||||
<Route path="/admin/tags/{id:Int}/edit" page={TagEditTagPage} name="editTag" />
|
||||
<Route path="/admin/tags/{id:Int}" page={TagTagPage} name="tag" />
|
||||
<Route path="/admin/tags" page={TagTagsPage} name="tags" />
|
||||
</Set>
|
||||
|
||||
<Set wrap={ScaffoldLayout} title="Socials" titleTo="socials" buttonLabel="New Social" buttonTo="newSocial">
|
||||
<Route path="/admin/socials/new" page={SocialNewSocialPage} name="newSocial" />
|
||||
<Route path="/admin/socials/{id:Int}/edit" page={SocialEditSocialPage} name="editSocial" />
|
||||
|
54
web/src/components/ColorPicker/ColorPicker.tsx
Normal file
54
web/src/components/ColorPicker/ColorPicker.tsx
Normal file
@ -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<React.SetStateAction<string>>
|
||||
}
|
||||
|
||||
const ColorPicker = ({ color, setColor }: ColorPickerProps) => {
|
||||
return (
|
||||
<div className="flex flex-col space-y-2 bg-base-100 p-2 rounded-xl w-min">
|
||||
<section className="w-52">
|
||||
<HexColorPicker color={color} onChange={setColor} />
|
||||
</section>
|
||||
<div className="flex space-x-2 w-52">
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||
<label className="input input-bordered flex items-center gap-2 input-sm w-full grow">
|
||||
<Icon path={mdiPound} className="size-4 opacity-70" />
|
||||
<HexColorInput color={color} className="w-16" />
|
||||
</label>
|
||||
<button
|
||||
className="btn btn-square btn-sm "
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(color)
|
||||
toast.success('Copied color to clipboard')
|
||||
} catch {
|
||||
toast.error(`Failed to copy, please try again`)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon path={mdiContentCopy} className="size-4" />
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-square btn-sm "
|
||||
onClick={async () => {
|
||||
try {
|
||||
setColor(await navigator.clipboard.readText())
|
||||
} catch {
|
||||
toast.error(`Failed to paste, please try again`)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon path={mdiContentPaste} className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ColorPicker
|
@ -20,17 +20,15 @@ interface PortraitFormProps {
|
||||
portrait?: Portrait
|
||||
}
|
||||
|
||||
export const QUERY: TypedDocumentNode<
|
||||
FindPortrait,
|
||||
FindPortraitVariables
|
||||
> = gql`
|
||||
query PortraitForm {
|
||||
portrait {
|
||||
id
|
||||
fileId
|
||||
export const QUERY: TypedDocumentNode<FindPortrait, FindPortraitVariables> =
|
||||
gql`
|
||||
query PortraitForm {
|
||||
portrait {
|
||||
id
|
||||
fileId
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
|
||||
const DELETE_PORTRAIT_MUTATION: TypedDocumentNode<
|
||||
DeletePortraitMutation,
|
||||
|
@ -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<EditProjectById> = gql`
|
||||
@ -42,10 +44,10 @@ const UPDATE_PROJECT_MUTATION: TypedDocumentNode<
|
||||
}
|
||||
`
|
||||
|
||||
export const Loading = () => <div>Loading...</div>
|
||||
export const Loading = () => <CellLoading />
|
||||
|
||||
export const Failure = ({ error }: CellFailureProps) => (
|
||||
<div className="rw-cell-error">{error?.message}</div>
|
||||
<CellFailure error={error} />
|
||||
)
|
||||
|
||||
export const Success = ({ project }: CellSuccessProps<EditProjectById>) => {
|
||||
|
@ -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 = () => <div>Loading...</div>
|
||||
export const Loading = () => <CellLoading />
|
||||
|
||||
export const Empty = () => <div>Project not found</div>
|
||||
export const Empty = () => <CellEmpty />
|
||||
|
||||
export const Failure = ({
|
||||
error,
|
||||
}: CellFailureProps<FindProjectByIdVariables>) => (
|
||||
<div className="rw-cell-error">{error?.message}</div>
|
||||
)
|
||||
}: CellFailureProps<FindProjectByIdVariables>) => <CellFailure error={error} />
|
||||
|
||||
export const Success = ({
|
||||
project,
|
||||
}: CellSuccessProps<FindProjectById, FindProjectByIdVariables>) => {
|
||||
return <Project project={project} />
|
||||
}
|
||||
}: CellSuccessProps<FindProjectById, FindProjectByIdVariables>) => (
|
||||
<Project project={project} />
|
||||
)
|
||||
|
@ -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<FindProjects, FindProjectsVariables> =
|
||||
gql`
|
||||
query FindProjects {
|
||||
projects {
|
||||
id
|
||||
title
|
||||
description
|
||||
date
|
||||
links
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
|
||||
export const Loading = () => <div>Loading...</div>
|
||||
export const Loading = () => <CellLoading />
|
||||
|
||||
export const Empty = () => {
|
||||
return (
|
||||
<div className="rw-text-center">
|
||||
{'No projects yet. '}
|
||||
<Link to={routes.newProject()} className="rw-link">
|
||||
{'Create one?'}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export const Empty = () => <CellEmpty />
|
||||
|
||||
export const Failure = ({ error }: CellFailureProps<FindProjects>) => (
|
||||
<div className="rw-cell-error">{error?.message}</div>
|
||||
<CellFailure error={error} />
|
||||
)
|
||||
|
||||
export const Success = ({
|
||||
projects,
|
||||
}: CellSuccessProps<FindProjects, FindProjectsVariables>) => {
|
||||
return <Projects projects={projects} />
|
||||
}
|
||||
}: CellSuccessProps<FindProjects, FindProjectsVariables>) => (
|
||||
<Projects projects={projects} />
|
||||
)
|
||||
|
@ -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 (
|
||||
<div className="flex w-full justify-center">
|
||||
|
@ -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<FindSocialById, FindSocialByIdVariables> =
|
||||
gql`
|
||||
query FindSocialById($id: Int!) {
|
||||
social: social(id: $id) {
|
||||
id
|
||||
name
|
||||
type
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
|
||||
export const Loading = () => <CellLoading />
|
||||
|
||||
|
@ -69,6 +69,9 @@ const SocialForm = (props: SocialFormProps) => {
|
||||
props.onSave(data, props?.social?.id)
|
||||
}
|
||||
|
||||
const nameRef = useRef<HTMLInputElement>(null)
|
||||
useEffect(() => nameRef.current?.focus(), [])
|
||||
|
||||
return (
|
||||
<Form<FormSocial>
|
||||
onSubmit={onSubmit}
|
||||
@ -90,6 +93,7 @@ const SocialForm = (props: SocialFormProps) => {
|
||||
</Label>
|
||||
<TextField
|
||||
name="name"
|
||||
ref={nameRef}
|
||||
placeholder="Name"
|
||||
defaultValue={props.social?.name}
|
||||
className="w-full"
|
||||
@ -125,8 +129,8 @@ const SocialForm = (props: SocialFormProps) => {
|
||||
type == 'email'
|
||||
? mdiAt
|
||||
: type == 'custom'
|
||||
? mdiLinkVariant
|
||||
: mdiAccount
|
||||
? mdiLinkVariant
|
||||
: mdiAccount
|
||||
}
|
||||
/>
|
||||
</Label>
|
||||
|
86
web/src/components/Tag/EditTagCell/EditTagCell.tsx
Normal file
86
web/src/components/Tag/EditTagCell/EditTagCell.tsx
Normal file
@ -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<EditTagById> = 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 = () => <CellLoading />
|
||||
|
||||
export const Failure = ({ error }: CellFailureProps) => (
|
||||
<CellFailure error={error} />
|
||||
)
|
||||
|
||||
export const Success = ({ tag }: CellSuccessProps<EditTagById>) => {
|
||||
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 (
|
||||
<div className="flex w-full justify-center">
|
||||
<div className="overflow-hidden overflow-x-auto rounded-xl bg-base-100">
|
||||
<table className="table">
|
||||
<thead className="bg-base-200 font-syne">
|
||||
<tr>
|
||||
<th>Edit Tag {tag?.id}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
<TagForm
|
||||
tag={tag}
|
||||
onSave={onSave}
|
||||
error={error}
|
||||
loading={loading}
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
58
web/src/components/Tag/NewTag/NewTag.tsx
Normal file
58
web/src/components/Tag/NewTag/NewTag.tsx
Normal file
@ -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 (
|
||||
<div className="flex w-full justify-center">
|
||||
<div className="overflow-hidden overflow-x-auto rounded-xl bg-base-100">
|
||||
<table className="table">
|
||||
<thead className="bg-base-200 font-syne">
|
||||
<tr>
|
||||
<th>New Tag</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
<TagForm onSave={onSave} error={error} loading={loading} />
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NewTag
|
104
web/src/components/Tag/Tag/Tag.tsx
Normal file
104
web/src/components/Tag/Tag/Tag.tsx
Normal file
@ -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<FindTagById['tag']>
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="flex w-full justify-center">
|
||||
<div className="w-80">
|
||||
<div className="overflow-hidden overflow-x-auto rounded-xl bg-base-100">
|
||||
<table className="table">
|
||||
<thead className="bg-base-200 font-syne">
|
||||
<th className="w-0">
|
||||
Tag {tag.id}: {tag.tag}
|
||||
</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<td>{tag.id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
<td>{tag.tag}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Color</th>
|
||||
<td>
|
||||
<div
|
||||
className="badge"
|
||||
style={{
|
||||
backgroundColor: tag.color,
|
||||
color:
|
||||
calculateLuminance(tag.color) > 0.5 ? 'black' : 'white',
|
||||
}}
|
||||
>
|
||||
{tag.color}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<nav className="my-2 flex justify-center space-x-2">
|
||||
<Link
|
||||
to={routes.editTag({ id: tag.id })}
|
||||
title={'Edit tag ' + tag.id}
|
||||
className="btn btn-primary btn-sm uppercase"
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
title={'Delete tag ' + tag.id}
|
||||
className="btn btn-error btn-sm uppercase"
|
||||
onClick={() => onDeleteClick(tag.id)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tag
|
34
web/src/components/Tag/TagCell/TagCell.tsx
Normal file
34
web/src/components/Tag/TagCell/TagCell.tsx
Normal file
@ -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<FindTagById, FindTagByIdVariables> = gql`
|
||||
query FindTagById($id: Int!) {
|
||||
tag: tag(id: $id) {
|
||||
id
|
||||
tag
|
||||
color
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const Loading = () => <CellLoading />
|
||||
|
||||
export const Empty = () => <CellEmpty />
|
||||
|
||||
export const Failure = ({ error }: CellFailureProps<FindTagByIdVariables>) => (
|
||||
<CellFailure error={error} />
|
||||
)
|
||||
|
||||
export const Success = ({
|
||||
tag,
|
||||
}: CellSuccessProps<FindTagById, FindTagByIdVariables>) => <Tag tag={tag} />
|
100
web/src/components/Tag/TagForm/TagForm.tsx
Normal file
100
web/src/components/Tag/TagForm/TagForm.tsx
Normal file
@ -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<EditTagById['tag']>
|
||||
|
||||
interface TagFormProps {
|
||||
tag?: EditTagById['tag']
|
||||
onSave: (data: UpdateTagInput, id?: FormTag['id']) => void
|
||||
error: RWGqlError
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const TagForm = (props: TagFormProps) => {
|
||||
const [tag, setTag] = useState<string>(props.tag?.tag ?? '')
|
||||
const [color, setColor] = useState<string>(props.tag?.color || '#e5e6e6')
|
||||
|
||||
const onSubmit = (data: FormTag) => {
|
||||
data.color = color
|
||||
props.onSave(data, props?.tag?.id)
|
||||
}
|
||||
|
||||
const tagRef = useRef<HTMLInputElement>(null)
|
||||
useEffect(() => tagRef.current?.focus(), [])
|
||||
|
||||
return (
|
||||
<Form<FormTag>
|
||||
onSubmit={onSubmit}
|
||||
error={props.error}
|
||||
className="max-w-56 space-y-2"
|
||||
>
|
||||
<Label name="tag" className="form-control w-full">
|
||||
<Label
|
||||
name="tag"
|
||||
className="input input-bordered flex items-center gap-2"
|
||||
errorClassName="input input-bordered flex items-center gap-2 input-error"
|
||||
>
|
||||
<Label
|
||||
name="tag"
|
||||
className="h-4 w-4 opacity-70"
|
||||
errorClassName="h-4 w-4 text-error"
|
||||
>
|
||||
<Icon path={mdiTag} />
|
||||
</Label>
|
||||
<TextField
|
||||
name="tag"
|
||||
ref={tagRef}
|
||||
placeholder="Tag"
|
||||
defaultValue={tag}
|
||||
className="w-full"
|
||||
maxLength={16}
|
||||
onChange={(e) => setTag(e.target.value.slice(0, 16))}
|
||||
validation={{
|
||||
required: {
|
||||
value: true,
|
||||
message: 'Required',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
<div className="label">
|
||||
<FieldError name="tag" className="text-xs font-semibold text-error" />
|
||||
</div>
|
||||
</Label>
|
||||
|
||||
<ColorPicker color={color} setColor={setColor} />
|
||||
|
||||
<div className="flex justify-center py-2">
|
||||
<div
|
||||
className={`badge ${tag.length === 0 && 'hidden'}`}
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
color: calculateLuminance(color) > 0.5 ? 'black' : 'white',
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="my-2 flex justify-center space-x-2">
|
||||
<Submit
|
||||
disabled={props.loading}
|
||||
className="btn btn-primary btn-sm uppercase"
|
||||
>
|
||||
Save
|
||||
</Submit>
|
||||
</nav>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagForm
|
191
web/src/components/Tag/Tags/Tags.tsx
Normal file
191
web/src/components/Tag/Tags/Tags.tsx
Normal file
@ -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 (
|
||||
<div className="w-full overflow-hidden overflow-x-auto rounded-xl bg-base-100">
|
||||
<table className="table">
|
||||
<thead className="bg-base-200 font-syne">
|
||||
<tr>
|
||||
<th className="w-0">Color</th>
|
||||
<th className="w-1/2">Tag</th>
|
||||
<th className="w-1/2">Preview</th>
|
||||
<th className="w-0"> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tags.map((tag, i) => {
|
||||
const actionButtons = (
|
||||
<>
|
||||
<Link
|
||||
to={routes.tag({ id: tag.id })}
|
||||
title={'Show tag ' + tag.id + ' detail'}
|
||||
className="btn btn-xs uppercase"
|
||||
>
|
||||
Show
|
||||
</Link>
|
||||
<Link
|
||||
to={routes.editTag({ id: tag.id })}
|
||||
title={'Edit social ' + tag.id}
|
||||
className="btn btn-primary btn-xs uppercase"
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
title={'Delete social ' + tag.id}
|
||||
className="btn btn-error btn-xs uppercase"
|
||||
onClick={() => onDeleteClick(tag.id)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<tr key={tag.id}>
|
||||
<th>
|
||||
<div
|
||||
key={i}
|
||||
className="tooltip tooltip-right"
|
||||
data-tip={tag.color}
|
||||
>
|
||||
<div
|
||||
className="size-8 rounded-md"
|
||||
style={{ backgroundColor: tag.color }}
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<td>{truncate(tag.tag)}</td>
|
||||
<td>
|
||||
<div
|
||||
className="badge"
|
||||
style={{
|
||||
backgroundColor: tag.color,
|
||||
color:
|
||||
calculateLuminance(tag.color) > 0.5 ? 'black' : 'white',
|
||||
}}
|
||||
>
|
||||
{tag.tag}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<nav className="hidden justify-end space-x-2 sm:flex">
|
||||
{actionButtons}
|
||||
</nav>
|
||||
<div className="dropdown dropdown-end flex justify-end sm:hidden">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="btn btn-square btn-ghost btn-sm lg:hidden"
|
||||
>
|
||||
<Icon
|
||||
path={mdiDotsVertical}
|
||||
className="text-base-content-100 size-6"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
||||
tabIndex={0}
|
||||
className="menu dropdown-content z-10 -mt-1 mr-10 inline rounded-box bg-base-100 shadow-xl"
|
||||
>
|
||||
<nav className="w-46 space-x-2">{actionButtons}</nav>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
// return (
|
||||
// <div className="rw-segment rw-table-wrapper-responsive">
|
||||
// <table className="rw-table">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// <th>Id</th>
|
||||
// <th>Tag</th>
|
||||
// <th>Color</th>
|
||||
// <th> </th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {tags.map((tag) => (
|
||||
// <tr key={tag.id}>
|
||||
// <td>{truncate(tag.id)}</td>
|
||||
// <td>{truncate(tag.tag)}</td>
|
||||
// <td>{truncate(tag.color)}</td>
|
||||
// <td>
|
||||
// <nav className="rw-table-actions">
|
||||
// <Link
|
||||
// to={routes.tag({ id: tag.id })}
|
||||
// title={'Show tag ' + tag.id + ' detail'}
|
||||
// className="rw-button rw-button-small"
|
||||
// >
|
||||
// Show
|
||||
// </Link>
|
||||
// <Link
|
||||
// to={routes.editTag({ id: tag.id })}
|
||||
// title={'Edit tag ' + tag.id}
|
||||
// className="rw-button rw-button-small rw-button-blue"
|
||||
// >
|
||||
// Edit
|
||||
// </Link>
|
||||
// <button
|
||||
// type="button"
|
||||
// title={'Delete tag ' + tag.id}
|
||||
// className="rw-button rw-button-small rw-button-red"
|
||||
// onClick={() => onDeleteClick(tag.id)}
|
||||
// >
|
||||
// Delete
|
||||
// </button>
|
||||
// </nav>
|
||||
// </td>
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// </div>
|
||||
// )
|
||||
}
|
||||
|
||||
export default TagsList
|
31
web/src/components/Tag/TagsCell/TagsCell.tsx
Normal file
31
web/src/components/Tag/TagsCell/TagsCell.tsx
Normal file
@ -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<FindTags, FindTagsVariables> = gql`
|
||||
query FindTags {
|
||||
tags {
|
||||
id
|
||||
tag
|
||||
color
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const Loading = () => <CellLoading />
|
||||
export const Empty = () => <CellEmpty />
|
||||
export const Failure = ({ error }: CellFailureProps<FindTags>) => (
|
||||
<CellFailure error={error} />
|
||||
)
|
||||
export const Success = ({
|
||||
tags,
|
||||
}: CellSuccessProps<FindTags, FindTagsVariables>) => <Tags tags={tags} />
|
@ -11,3 +11,7 @@
|
||||
/**
|
||||
* END --- SETUP TAILWINDCSS EDIT
|
||||
*/
|
||||
|
||||
.w-52 .react-colorful {
|
||||
width: 13rem;
|
||||
}
|
||||
|
@ -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(),
|
||||
|
12
web/src/lib/color.tsx
Normal file
12
web/src/lib/color.tsx
Normal file
@ -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
|
||||
}
|
@ -30,7 +30,7 @@ const ContactPage = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Metadata title={`${process.env.NAME} | Contact`} />
|
||||
<Metadata title="Contact" />
|
||||
|
||||
<div className="flex min-h-[calc(100vh-6rem)] items-center justify-center">
|
||||
<ContactCardCell />
|
||||
|
@ -3,7 +3,7 @@ import { Metadata } from '@redwoodjs/web'
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<>
|
||||
<Metadata title="Home" description="Home page" />
|
||||
<Metadata title="Home" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -24,10 +24,8 @@ const LoginPage = () => {
|
||||
if (isAuthenticated) navigate(routes.home())
|
||||
}, [isAuthenticated])
|
||||
|
||||
const emailRef = useRef<HTMLInputElement>(null)
|
||||
useEffect(() => {
|
||||
emailRef.current?.focus()
|
||||
}, [])
|
||||
const usernameRef = useRef<HTMLInputElement>(null)
|
||||
useEffect(() => usernameRef.current?.focus(), [])
|
||||
|
||||
const onSubmit = async (data: Record<string, string>) => {
|
||||
const response = await logIn({
|
||||
@ -53,13 +51,14 @@ const LoginPage = () => {
|
||||
>
|
||||
<Label
|
||||
name="username"
|
||||
className="h-4 w-4 opacity-70"
|
||||
errorClassName="h-4 w-4 text-error"
|
||||
className="size-4 opacity-70"
|
||||
errorClassName="size-4 text-error"
|
||||
>
|
||||
<Icon path={mdiAccount} />
|
||||
</Label>
|
||||
<TextField
|
||||
name="username"
|
||||
ref={usernameRef}
|
||||
className="grow"
|
||||
placeholder="Username"
|
||||
validation={{
|
||||
@ -79,8 +78,8 @@ const LoginPage = () => {
|
||||
>
|
||||
<Label
|
||||
name="password"
|
||||
className="h-4 w-4 opacity-70"
|
||||
errorClassName="h-4 w-4 text-error"
|
||||
className="size-4 opacity-70"
|
||||
errorClassName="size-4 text-error"
|
||||
>
|
||||
<Icon path={mdiKey} />
|
||||
</Label>
|
||||
@ -104,7 +103,7 @@ const LoginPage = () => {
|
||||
<FieldError name="password" className="text-sm text-error" />
|
||||
|
||||
<div className="flex w-full">
|
||||
<Submit className="btn btn-primary mx-auto">Log In</Submit>
|
||||
<Submit className="btn btn-primary btn-sm mx-auto">Log In</Submit>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
|
@ -1,12 +1,17 @@
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
|
||||
import ThemeToggle from 'src/components/ThemeToggle/ThemeToggle'
|
||||
|
||||
export default () => (
|
||||
<div className="flex h-screen items-center justify-center space-x-2 font-inter">
|
||||
<Link to={routes.home()} className="btn btn-primary">
|
||||
404
|
||||
</Link>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<>
|
||||
<Metadata title="404" />
|
||||
|
||||
<div className="flex h-screen items-center justify-center space-x-2 font-inter">
|
||||
<Link to={routes.home()} className="btn btn-primary">
|
||||
404
|
||||
</Link>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
|
||||
import PortraitCell from 'src/components/Portrait/PortraitCell'
|
||||
|
||||
const PortraitPage = () => <PortraitCell />
|
||||
const PortraitPage = () => (
|
||||
<>
|
||||
<Metadata title="Portrait" />
|
||||
<PortraitCell />
|
||||
</>
|
||||
)
|
||||
|
||||
export default PortraitPage
|
||||
|
@ -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 <EditProjectCell id={id} />
|
||||
}
|
||||
const EditProjectPage = ({ id }: ProjectPageProps) => (
|
||||
<>
|
||||
<Metadata title={`Edit Project ${id}`} />
|
||||
<EditProjectCell id={id} />
|
||||
</>
|
||||
)
|
||||
|
||||
export default EditProjectPage
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
|
||||
import NewProject from 'src/components/Project/NewProject'
|
||||
|
||||
const NewProjectPage = () => {
|
||||
return <NewProject />
|
||||
}
|
||||
const NewProjectPage = () => (
|
||||
<>
|
||||
<Metadata title="New Project" />
|
||||
<NewProject />
|
||||
</>
|
||||
)
|
||||
|
||||
export default NewProjectPage
|
||||
|
@ -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 <ProjectCell id={id} />
|
||||
}
|
||||
const ProjectPage = ({ id }: ProjectPageProps) => (
|
||||
<>
|
||||
<Metadata title={`Project ${id}`} />
|
||||
<ProjectCell id={id} />
|
||||
</>
|
||||
)
|
||||
|
||||
export default ProjectPage
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
|
||||
import ProjectsCell from 'src/components/Project/ProjectsCell'
|
||||
|
||||
const ProjectsPage = () => {
|
||||
return <ProjectsCell />
|
||||
}
|
||||
const ProjectsPage = () => (
|
||||
<>
|
||||
<Metadata title="Projects" />
|
||||
<ProjectsCell />
|
||||
</>
|
||||
)
|
||||
|
||||
export default ProjectsPage
|
||||
|
@ -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 <EditSocialCell id={id} />
|
||||
}
|
||||
const EditSocialPage = ({ id }: SocialPageProps) => (
|
||||
<>
|
||||
<Metadata title={`Edit Social ${id}`} />
|
||||
<EditSocialCell id={id} />
|
||||
</>
|
||||
)
|
||||
|
||||
export default EditSocialPage
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
|
||||
import NewSocial from 'src/components/Social/NewSocial/NewSocial'
|
||||
|
||||
const NewSocialPage = () => <NewSocial />
|
||||
const NewSocialPage = () => (
|
||||
<>
|
||||
<Metadata title="New Social" />
|
||||
<NewSocial />
|
||||
</>
|
||||
)
|
||||
|
||||
export default NewSocialPage
|
||||
|
@ -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 <SocialCell id={id} />
|
||||
}
|
||||
const SocialPage = ({ id }: SocialPageProps) => (
|
||||
<>
|
||||
<Metadata title={`Social ${id}`} />
|
||||
<SocialCell id={id} />
|
||||
</>
|
||||
)
|
||||
|
||||
export default SocialPage
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
|
||||
import SocialsCell from 'src/components/Social/SocialsCell'
|
||||
|
||||
const SocialsPage = () => {
|
||||
return <SocialsCell />
|
||||
}
|
||||
const SocialsPage = () => (
|
||||
<>
|
||||
<Metadata title="Socials" />
|
||||
<SocialsCell />
|
||||
</>
|
||||
)
|
||||
|
||||
export default SocialsPage
|
||||
|
16
web/src/pages/Tag/EditTagPage/EditTagPage.tsx
Normal file
16
web/src/pages/Tag/EditTagPage/EditTagPage.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
|
||||
import EditTagCell from 'src/components/Tag/EditTagCell'
|
||||
|
||||
type TagPageProps = {
|
||||
id: number
|
||||
}
|
||||
|
||||
const EditTagPage = ({ id }: TagPageProps) => (
|
||||
<>
|
||||
<Metadata title={`Edit Tag ${id}`} />
|
||||
<EditTagCell id={id} />
|
||||
</>
|
||||
)
|
||||
|
||||
export default EditTagPage
|
12
web/src/pages/Tag/NewTagPage/NewTagPage.tsx
Normal file
12
web/src/pages/Tag/NewTagPage/NewTagPage.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
|
||||
import NewTag from 'src/components/Tag/NewTag'
|
||||
|
||||
const NewTagPage = () => (
|
||||
<>
|
||||
<Metadata title="New Tag" />
|
||||
<NewTag />
|
||||
</>
|
||||
)
|
||||
|
||||
export default NewTagPage
|
16
web/src/pages/Tag/TagPage/TagPage.tsx
Normal file
16
web/src/pages/Tag/TagPage/TagPage.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
|
||||
import TagCell from 'src/components/Tag/TagCell'
|
||||
|
||||
type TagPageProps = {
|
||||
id: number
|
||||
}
|
||||
|
||||
const TagPage = ({ id }: TagPageProps) => (
|
||||
<>
|
||||
<Metadata title={`Tag ${id}`} />
|
||||
<TagCell id={id} />
|
||||
</>
|
||||
)
|
||||
|
||||
export default TagPage
|
12
web/src/pages/Tag/TagsPage/TagsPage.tsx
Normal file
12
web/src/pages/Tag/TagsPage/TagsPage.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
|
||||
import TagsCell from 'src/components/Tag/TagsCell'
|
||||
|
||||
const TagsPage = () => (
|
||||
<>
|
||||
<Metadata title="Tags" />
|
||||
<TagsCell />
|
||||
</>
|
||||
)
|
||||
|
||||
export default TagsPage
|
11
yarn.lock
11
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
|
||||
|
Reference in New Issue
Block a user