1
0
Fork 0

feat(org): Handle author claims for posts

This commit is contained in:
joostdecock 2024-02-25 16:01:03 +01:00
parent 46f884de69
commit 9610d7d11b
3 changed files with 180 additions and 24 deletions

View file

@ -1,3 +1,7 @@
import { useContext, useState } from 'react'
import { ModalContext } from 'shared/context/modal-context.mjs'
import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs'
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
import { cloudflareImageUrl, nsMerge } from 'shared/utils.mjs' import { cloudflareImageUrl, nsMerge } from 'shared/utils.mjs'
import { makers } from 'site/prebuild/makers.mjs' import { makers } from 'site/prebuild/makers.mjs'
// Components // Components
@ -6,6 +10,7 @@ import { Lightbox } from 'shared/components/lightbox.mjs'
import { ImageWrapper } from 'shared/components/wrappers/img.mjs' import { ImageWrapper } from 'shared/components/wrappers/img.mjs'
import { TimeAgo, ns as timeagoNs } from 'shared/components/timeago/index.mjs' import { TimeAgo, ns as timeagoNs } from 'shared/components/timeago/index.mjs'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { Link } from 'shared/components/link.mjs'
import { import {
BaseLayout, BaseLayout,
BaseLayoutLeft, BaseLayoutLeft,
@ -23,8 +28,12 @@ import { Toc, ns as tocNs } from 'shared/components/mdx/toc.mjs'
import { PrevNext } from 'shared/components/prev-next.mjs' import { PrevNext } from 'shared/components/prev-next.mjs'
import { Tag } from 'shared/components/tag.mjs' import { Tag } from 'shared/components/tag.mjs'
import { UserProfile } from 'shared/components/user-profile.mjs' import { UserProfile } from 'shared/components/user-profile.mjs'
import { useAccount } from 'shared/hooks/use-account.mjs'
import { useBackend } from 'shared/hooks/use-backend.mjs'
import { MarkdownInput } from 'shared/components/inputs.mjs'
import { userCard } from 'shared/components/support/support.mjs'
export const ns = nsMerge(navNs, tocNs, timeagoNs, 'docs') export const ns = nsMerge(navNs, tocNs, timeagoNs, 'docs', 'account')
const PostMeta = ({ frontmatter, t }) => ( const PostMeta = ({ frontmatter, t }) => (
<div className="flex flex-row justify-between text-sm mb-1 mt-2"> <div className="flex flex-row justify-between text-sm mb-1 mt-2">
@ -70,13 +79,126 @@ export const PostImage = ({ imgId, frontmatter }) => (
</figure> </figure>
) )
const createIssue = async ({ account, setLoadingStatus, title, body, backend, setModal }) => {
setLoadingStatus([true, 'account:oneMomentPlease'])
const issueData = {
title,
body: account ? `${body}\n\n${userCard(account.id || false)}` : body,
labels: ['%3A%2B1%3A+good+first+issue'],
}
const result = await backend.createIssue(issueData)
if (result.success) {
setLoadingStatus([true, 'account:nailedIt', true, true])
setModal(
<ModalWrapper flex="col" justify="top lg:justify-center" slideFrom="right" keepOpenOnClick>
<div className="max-w-prose mdx">
<h2>Issue created</h2>
<p>Thank you for helping out.</p>
<p>
We <a href={result.data.issue.html_url}>created a new issue for this</a>.
<br />
If you would like to help out even more, the issue describes what file to change and
what change needs to be made.
</p>
<p>
You can do this via the GitHub website, so it is a great way to make a first
contribution if you are new to open source.
</p>
</div>
</ModalWrapper>
)
} else setLoadingStatus([true, 'backendError', true, false])
}
export const PostContent = ({ mdx, dir }) => ( export const PostContent = ({ mdx, dir }) => (
<div className="strapi prose lg:prose-lg mb-12 m-auto"> <div className="strapi prose lg:prose-lg mb-12 m-auto">
<MdxWrapper mdx={mdx} slug={`blog/${dir}`} /> <MdxWrapper mdx={mdx} slug={`blog/${dir}`} />
</div> </div>
) )
const ClaimThisPost = ({ t, type }) => ( const ClaimAuthor = ({ t, type }) => (
<div className="max-w-prose">
<h2>{t(`docs:i${type === 'blog' ? 'Wrote' : 'Made'}This`)}</h2>
<p>Great, but it looks like you are not currently logged in.</p>
<p>Please log in and then claim this post so we know what account to associate it with.</p>
<p className="text-center">
<Link href="/signin" className="btn btn-primary">
Sign In
</Link>
</p>
</div>
)
const issueData = ({ type, dir, account, body = false }) => ({
title: body
? `An author suggestion was submitted for the ${type} post ${dir}`
: `The ${type} post ${dir} was claimed as their own by user ${account.id}`,
body: `This issue is about who should get credit for [this ${type} post](https://freesewing.org/${type}/${dir}).
According to [user ${account.username}](https://freesewing.org.users/user?id=${account.id}) with ID ${account.id},
${body ? 'who wrote:\n\n---\n\n' + body + '\n\n---\n\n' : 'who claimed it as their own'}.
To reflect this on the site, update [this markdown file](https://github.com/freesewing/freesewing/blob/develop/markdown/org/${type}/${dir}/en.md) so that the frontmatter includes this:
\`\`\`md
author: ${body ? 'the FreeSewing user ID' : account.id}
\`\`\`
Anyone can do this, so if you're looking to contribute, this is a great way to get started.`,
})
const SuggestAuthor = ({ t, type, setLoadingStatus, backend, dir, setModal }) => {
const { account } = useAccount()
const [body, setBody] = useState('')
return (
<>
<h2>{t(`docs:iKnowWho${type === 'blog' ? 'Wrote' : 'Made'}This`)}</h2>
<p>Awesome. Please let us know below who it was by providing either:</p>
<ul className="list list-inside list-disc ml-4">
<li>
Their FreeSewing <b>user id</b> (best)
</li>
<li>
Their FreeSewing <b>username</b> (good)
</li>
<li>Other info that allows us to figure out who it is</li>
</ul>
<MarkdownInput
id="support-body"
label={t('support:description')}
update={setBody}
current={body}
valid={(val) => val.length > 10}
/>
<p>When you are done, click the button below to submit.</p>
<p className="text-center">
<button
className="btn btn-primary w-full"
onClick={() =>
createIssue({
account,
backend,
setLoadingStatus,
setModal,
...issueData({ type, dir, account, body }),
})
}
>
Submit
</button>
</p>
</>
)
}
const ClaimThisPost = ({ t, type, dir }) => {
const { setModal } = useContext(ModalContext)
const { account } = useAccount()
const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext)
return (
<div id="maker" className="p-4 border rounded-lg shadow"> <div id="maker" className="p-4 border rounded-lg shadow">
<h3>Claim this post</h3> <h3>Claim this post</h3>
<p> <p>
@ -84,27 +206,60 @@ const ClaimThisPost = ({ t, type }) => (
proper credit: proper credit:
</p> </p>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<button className="btn btn-primary btn-outline">I know who wrote this</button> <button
<button className="btn btn-primary">I wrote this</button> className="btn btn-primary btn-outline"
onClick={() =>
setModal(
<ModalWrapper
flex="col"
justify="top lg:justify-center"
slideFrom="right"
keepOpenOnClick
>
<SuggestAuthor
{...{ t, type, setLoadingStatus, dir, account, backend, setModal }}
/>
</ModalWrapper>
)
}
>
{t(`docs:iKnowWho${type === 'blog' ? 'Wrote' : 'Made'}This`)}
</button>
<button
className="btn btn-primary"
onClick={
account.id
? () =>
createIssue({
account,
backend,
setLoadingStatus,
setModal,
...issueData({ type, dir, account }),
})
: () =>
setModal(
<ModalWrapper flex="col" justify="top lg:justify-center" slideFrom="right">
<ClaimAuthor {...{ t, type, setLoadingStatus, dir, backend, setModal }} />
</ModalWrapper>
)
}
>
{t(`docs:i${type === 'blog' ? 'Wrote' : 'Made'}This`)}
</button>
</div> </div>
</div> </div>
) )
}
const Maker = ({ id, type, t }) => const Maker = ({ id, type, t, dir }) =>
makers[id] ? ( makers[id] ? (
<div id="maker" className="p-4 border rounded-lg shadow"> <div id="maker" className="p-4 border rounded-lg shadow">
<h5 <h5 className="text-center">{t(`docs:${type === 'blog' ? 'writtenBy' : 'madeBy'}`)}</h5>
dangerouslySetInnerHTML={{
__html:
t(type === 'blog' ? 'docs:xWroteThis' : 'docs:xMadeThis', { x: makers[id].username }) +
':',
}}
className="text-center"
/>
<UserProfile user={makers[id]} /> <UserProfile user={makers[id]} />
</div> </div>
) : ( ) : (
<ClaimThisPost t={t} type={type} /> <ClaimThisPost t={t} type={type} dir={dir} />
) )
/** layout for a page that displays a blog, showcase or newsletter */ /** layout for a page that displays a blog, showcase or newsletter */
@ -138,6 +293,7 @@ export const PostLayout = ({ mdx, frontmatter, type, dir }) => {
id={type === 'blog' ? frontmatter.author : frontmatter.maker} id={type === 'blog' ? frontmatter.author : frontmatter.maker}
type={type} type={type}
t={t} t={t}
dir={dir}
/> />
<PrevNext /> <PrevNext />
</article> </article>

View file

@ -47,7 +47,7 @@ const types = [
'other', 'other',
] ]
const userCard = (id) => export const userCard = (id) =>
`[![User ${id}](${config.backend}/users/${id}/card)](https://next.freesewing.org/users/${id})` `[![User ${id}](${config.backend}/users/${id}/card)](https://next.freesewing.org/users/${id})`
const templates = { const templates = {

View file

@ -23,8 +23,8 @@ credits: Credits
contentsBy: Contents by contentsBy: Contents by
translators: Translators translators: Translators
title: Title title: Title
xMadeThis: "<strong>{x}</strong> made this" writtenBy: Written by
xWroteThis: "<strong>{x}</strong> wrote this" madeBy: Made by
by: By by: By
claimThisPost: Claim this post claimThisPost: Claim this post
iKnowWhoMadeThis: I know who made this iKnowWhoMadeThis: I know who made this