Skip to content

Commit

Permalink
Feat: Prod Social Feed Widget (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
roshaans authored May 31, 2023
1 parent fd34769 commit 5e4f24c
Show file tree
Hide file tree
Showing 10 changed files with 929 additions and 8 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/deploy-dev-widgets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ on:
push:
branches: [main]
paths:
- "frontend/widgets/src/**"
- "frontend/widgets/**"
jobs:
deploy-mainnet:
uses: ./.github/workflows/deploy-widgets.yml
with:
directory-path: frontend/widgets/
directory-paths: ${{vars.WIDGETS_DIRECTORY_PATHS}}
deploy-account-address: ${{ vars.DEV_SIGNER_ACCOUNT_ID }}
signer-public-key: ${{ vars.DEV_SIGNER_PUBLIC_KEY }}
secrets:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-prod-widgets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
deploy-mainnet:
uses: ./.github/workflows/deploy-widgets.yml
with:
directory-path: frontend/widgets/
directory-paths: ${{vars.WIDGETS_DIRECTORY_PATHS}}
deploy-account-address: ${{ vars.PROD_SIGNER_ACCOUNT_ID }}
signer-public-key: ${{ vars.PROD_SIGNER_PUBLIC_KEY }}
secrets:
Expand Down
14 changes: 9 additions & 5 deletions .github/workflows/deploy-widgets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ on:
required: true
description: "Public key for signing transactions in the format: `ed25519:<public_key>`"
type: string
directory-path:
directory-paths:
required: true
description: "Path to the directory that contains the code to be deployed"
description: "Comma-separated paths to the directories that contain the code to be deployed"
type: string
secrets:
SIGNER_PRIVATE_KEY:
Expand All @@ -31,7 +31,7 @@ jobs:
BOS_DEPLOY_ACCOUNT_ID: ${{ inputs.deploy-account-address }}
BOS_SIGNER_PUBLIC_KEY: ${{ inputs.signer-public-key }}
BOS_SIGNER_PRIVATE_KEY: ${{ secrets.SIGNER_PRIVATE_KEY }}
DIRECTORY_PATH: ${{ inputs.directory-path }}
DIRECTORY_PATHS: ${{ inputs.directory-paths }}

steps:
- name: Checkout repository
Expand All @@ -43,5 +43,9 @@ jobs:
- name: Deploy widgets
run: |
cd $DIRECTORY_PATH
bos components deploy "$BOS_DEPLOY_ACCOUNT_ID" sign-as "$BOS_DEPLOY_ACCOUNT_ID" network-config mainnet sign-with-plaintext-private-key --signer-public-key "$BOS_SIGNER_PUBLIC_KEY" --signer-private-key "$BOS_SIGNER_PRIVATE_KEY" send
for DIR in $(echo $DIRECTORY_PATHS | tr "," "\n")
do
cd "$DIR"
bos components deploy "$BOS_DEPLOY_ACCOUNT_ID" sign-as "$BOS_DEPLOY_ACCOUNT_ID" network-config mainnet sign-with-plaintext-private-key --signer-public-key "$BOS_SIGNER_PUBLIC_KEY" --signer-private-key "$BOS_SIGNER_PRIVATE_KEY" send
cd -
done
220 changes: 220 additions & 0 deletions frontend/widgets/examples/feed/src/QueryApi.Examples.Feed.Comment.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
const GRAPHQL_ENDPOINT =
"https://queryapi-hasura-graphql-24ktefolwq-ew.a.run.app";
const APP_OWNER = "dev-queryapi.dataplatform.near";
const accountId = props.accountId;
const blockHeight =
props.blockHeight === "now" ? "now" : parseInt(props.blockHeight);

State.init({
content: JSON.parse(props.content) ?? undefined,
notifyAccountId: undefined,
});

// const parentItem = content.item;
const highlight = !!props.highlight;

const extractNotifyAccountId = (parentItem) => {
if (!parentItem || parentItem.type !== "social" || !parentItem.path) {
return undefined;
}
const accountId = parentItem.path.split("/")[0];
return `${accountId}/post/main` === parentItem.path ? accountId : undefined;
};

const commentUrl = `https://alpha.near.org/#/${APP_OWNER}/widget/QueryApi.Examples.Feed.PostPage?accountId=${accountId}&commentBlockHeight=${blockHeight}`;

if (!state.content && accountId && blockHeight !== "now") {
const commentQuery = `
query CommentQuery {
roshaan_near_feed_indexer_comments(
where: {_and: {account_id: {_eq: "${accountId}"}, block_height: {_eq: ${blockHeight}}}}
) {
content
block_timestamp
receipt_id
post {
account_id
}
}
}
`;

function fetchGraphQL(operationsDoc, operationName, variables) {
return asyncFetch(
`${GRAPHQL_ENDPOINT}/v1/graphql`,
{
method: "POST",
headers: { "x-hasura-role": "roshaan_near" },
body: JSON.stringify({
query: operationsDoc,
variables: variables,
operationName: operationName,
}),
}
);
}

fetchGraphQL(commentQuery, "CommentQuery", {}).then((result) => {
if (result.status === 200) {
if (result.body.data) {
const comments = result.body.data.roshaan_near_feed_indexer_comments;
if (comments.length > 0) {
const comment = comments[0];
let content = JSON.parse(comment.content);
State.update({
content: content,
notifyAccountId: comment.post.accountId,
});
}
}
}
});
}

const Comment = styled.div`
position: relative;
&::before {
content: '';
display: block;
position: absolute;
left: 15px;
top: 44px;
bottom: 12px;
width: 2px;
background: ${props.highlight ? "#006ADC" : "#ECEEF0"};
}
`;

const Header = styled.div`
display: inline-flex;
margin-bottom: 0;
`;

const Body = styled.div`
padding-left: 44px;
padding-bottom: 1px;
`;

const Content = styled.div`
img {
display: block;
max-width: 100%;
max-height: 80vh;
margin: 0 0 12px;
}
`;

const Text = styled.p`
display: block;
margin: 0;
font-size: 14px;
line-height: 20px;
font-weight: 400;
color: #687076;
white-space: nowrap;
`;

const Actions = styled.div`
display: flex;
align-items: center;
gap: 12px;
margin: -6px -6px 6px;
`;

return (
<Comment>
<Header>
<Widget
src="calebjacob.near/widget/AccountProfile"
props={{
accountId,
avatarSize: "32px",
hideAccountId: true,
inlineContent: (
<>
<Text as="span"></Text>
{blockHeight === "now" ? (
"now"
) : (
<Text>
<Widget
src="mob.near/widget/TimeAgo"
props={{ blockHeight }}
/>{" "}
ago
</Text>
)}
</>
),
}}
/>
</Header>

<Body>
<Content>
{state.content.text && (
<Widget
src="calebjacob.near/widget/SocialMarkdown"
props={{ text: state.content.text }}
/>
)}

{state.content.image && (
<Widget
src="mob.near/widget/Image"
props={{
image: state.content.image,
}}
/>
)}
</Content>

{blockHeight !== "now" && (
<Actions>
<Widget
src={`${APP_OWNER}/widget/QueryApi.Examples.Feed.LikeButton`}
props={{
item: {
type: "social",
path: `${accountId}/post/comment`,
blockHeight,
},
notifyAccountId: state.notifyAccountId,
likes: [],
GRAPHQL_ENDPOINT,
APP_OWNER,
}}
/>
<Widget
src="calebjacob.near/widget/CommentButton"
props={{
hideCount: true,
onClick: () => State.update({ showReply: !state.showReply }),
}}
/>
<Widget
src="calebjacob.near/widget/CopyUrlButton"
props={{
url: commentUrl,
}}
/>
</Actions>
)}

{state.showReply && (
<div className="mb-2">
<Widget
src="calebjacob.near/widget/Comments.Compose"
props={{
initialText: `@${accountId}, `,
notifyAccountId: state.notifyAccountId,
item: parentItem,
onComment: () => State.update({ showReply: false }),
}}
/>
</div>
)}
</Body>
</Comment>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@

const GRAPHQL_ENDPOINT =
"https://queryapi-hasura-graphql-24ktefolwq-ew.a.run.app";
const APP_OWNER = "dev-queryapi.dataplatform.near";
const item = props.item;

const likes = JSON.parse(props.likes?.length ? props.likes : "[]") ?? [];

State.init({ likes: props.likes });

if (!item) {
return "";
}

const dataLoading = likes === null;

const likesByUsers = {};

(state.likes || []).forEach((account_id) => {
likesByUsers[account_id] = true;
});

if (state.hasLike === true) {
likesByUsers[context.accountId] = {
accountId: context.accountId,
};
} else if (state.hasLike === false) {
delete likesByUsers[context.accountId];
}

const accountsWithLikes = Object.keys(likesByUsers);
const hasLike = context.accountId && !!likesByUsers[context.accountId];
const hasLikeOptimistic =
state.hasLikeOptimistic === undefined ? hasLike : state.hasLikeOptimistic;
const totalLikes =
accountsWithLikes.length +
(hasLike === false && state.hasLikeOptimistic === true ? 1 : 0) -
(hasLike === true && state.hasLikeOptimistic === false ? 1 : 0);

const LikeButton = styled.button`
border: 0;
display: inline-flex;
align-items: center;
gap: 6px;
color: #687076;
font-weight: 400;
font-size: 14px;
line-height: 17px;
cursor: pointer;
background: none;
padding: 6px;
transition: color 200ms;
i {
font-size: 16px;
transition: color 200ms;
&.bi-heart-fill {
color: #E5484D !important;
}
}
&:hover, &:focus {
outline: none;
color: #11181C;
}
`;

const likeClick = () => {
if (state.loading) {
return;
}

State.update({
loading: true,
hasLikeOptimistic: !hasLike,
});

const data = {
index: {
like: JSON.stringify({
key: item,
value: {
type: hasLike ? "unlike" : "like",
},
}),
},
};

if (!hasLike && props.notifyAccountId) {
data.index.notify = JSON.stringify({
key: props.notifyAccountId,
value: {
type: "like",
item,
},
});
}
Social.set(data, {
onCommit: () => State.update({ loading: false, hasLike: !hasLike }),
onCancel: () =>
State.update({
loading: false,
hasLikeOptimistic: !state.hasLikeOptimistic,
}),
});
};

const title = hasLike ? "Unlike" : "Like";

return (
<LikeButton
disabled={state.loading || dataLoading || !context.accountId}
title={title}
onClick={likeClick}
>
<i className={`${hasLikeOptimistic ? "bi-heart-fill" : "bi-heart"}`} />
{totalLikes}
</LikeButton>
);
Loading

0 comments on commit 5e4f24c

Please sign in to comment.