Skip to content

Commit

Permalink
Merge pull request #31 from HashMapsData2Value/feat/use-wallet
Browse files Browse the repository at this point in the history
feat: use wallet
  • Loading branch information
HashMapsData2Value authored Sep 17, 2024
2 parents e46f8f6 + d747247 commit 4e211c8
Show file tree
Hide file tree
Showing 41 changed files with 2,225 additions and 644 deletions.
Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- <meta name="viewport" content="width=device-width, initial-scale=1" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!-- <meta name="theme-color" content="#000000" /> -->
<meta name="description" content="Edaga - Social Messaging on Algorand" />
<link rel="apple-touch-icon" href="/favicon.svg" />
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"homepage": "https://hashmapsdata2value.github.io/edaga/",
"dependencies": {
"@agoralabs-sh/avm-web-provider": "^1.6.2",
"@bitjson/qr-code": "^1.0.2",
"@blockshake/defly-connect": "^1.1.6",
"@perawallet/connect": "^1.3.4",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1",
Expand All @@ -24,10 +26,12 @@
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@txnlab/use-wallet-react": "^3.1.3",
"@txnlab/use-wallet-react": "^3.2.1",
"@walletconnect/core": "^2.15.1",
"@walletconnect/modal": "^2.6.2",
"@walletconnect/sign-client": "^2.15.0",
"algosdk": "^2.8.0",
"@walletconnect/web3wallet": "^1.14.1",
"algosdk": "^2.9.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
Expand All @@ -39,6 +43,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.25.1",
"react-svg": "^16.1.34",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"zustand": "^4.5.4"
Expand Down
Binary file modified public/192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions src/Routes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { createBrowserRouter } from "react-router-dom";

import { TransactionProvider } from "@/context/TransactionContext";

import All from "@/components/views/All";
import Replies from "@/components/views/Replies";
import Topics from "@/components/views/Topics";
// import Topic from "@/components/views/Topic";

export const router = createBrowserRouter(
[
{
path: "/",
element: (
<TransactionProvider>
<All />
</TransactionProvider>
),
},
{
path: "replies/:originalTxId",
element: (
<TransactionProvider>
<Replies />
</TransactionProvider>
),
},
{
path: "topics/",
element: (
<TransactionProvider>
<Topics />
</TransactionProvider>
),
children: [
{
path: "replies/:originalTxId",
element: (
<TransactionProvider>
<Replies />
</TransactionProvider>
),
},
],
// TODO - Topics sub-view
// children: [
// {
// path: ":topic",
// element: (
// <TransactionProvider>
// <Topic />
// </TransactionProvider>
// ),
// },
// ],
},
],
{
basename: "",
}
);
14 changes: 14 additions & 0 deletions src/assets/data/quotes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const quotes = [
`“Ultimately, literature is nothing but carpentry. With both you are working with reality, a material just as hard as wood”, Gabriel García Márquez`,
`“I have to write things down to feel I fully comprehend them”, Haruki Murakami`,
`“Writing is nothing more than a guided dream”, Jorge Luis Borges`,
`“A book must be the axe for the frozen sea within us”, Franz Kafka`,
`“The storyteller who tells stories to help us better understand ourselves and our world”, Chinua Achebe`,
`“Writing is a way to talk without being interrupted”, Marguerite Yourcenar`,
`“Write what should not be forgotten”, Isabel Allende`,
`“You write in order to change the world, knowing perfectly well that you probably can’t, but also knowing that literature is indispensable to the world”, James Baldwin`,
`“Writing is a struggle against silence”, Nawal El Saadawi`,
`“Writing is an act of faith; I believe it’s also an act of hope, the hope that things can get better than they are”, Octavio Paz`,
`“The real voyage of discovery consists not in seeking new landscapes, but in having new eyes”, Marcel Proust`,
`“A word after a word after a word is power”, Margaret Atwood`,
];
269 changes: 269 additions & 0 deletions src/components/app/Compose.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import { useWallet } from "@txnlab/use-wallet-react";
import algosdk from "algosdk";
import { useState, useEffect, useMemo } from "react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet";
import { Textarea } from "@/components/ui/textarea";
import { CornerDownLeft as IconCornerDownLeft } from "lucide-react";
import { UpdateIcon } from "@radix-ui/react-icons";
import { useApplicationState } from "@/store";
import { useTransactionContext } from "@/context/TransactionContext";
import { quotes } from "@/assets/data/quotes";
import { Input } from "../ui/input";

interface ComposeProps {
open: boolean;
onOpenChange: (open: boolean) => void;
isTopic?: boolean;
isReply?: boolean;
replyToTxId?: string;
// currentPath: string;
}

const Compose = ({
open,
onOpenChange,
isTopic,
isReply,
replyToTxId,
}: ComposeProps) => {
const {
algodClient,
activeAddress,
// activeNetwork,
// setActiveNetwork,
transactionSigner,
} = useWallet();

const { broadcastChannel, handles } = useApplicationState();
const { loadTransactions } = useTransactionContext();

const [message, setMessage] = useState("");
const maxMessageLength = 800;
const [topicName, setTopicName] = useState("");
const maxTopicLength = 60;
const [isSending, setIsSending] = useState(false);

const activeHandle = activeAddress ? handles[activeAddress] || "" : "";

/**
* As the longest fixed part of a message is a reply:
* `ARC00-0;r;0000000000000000000000000000000000000000000000000000;;` (64 characters)
*
* ..and the longest NFD (including segment) is:
* `{27}.{27}.algo` (60 characters*)
*
* So, that's:
* ARC00-0;r;0000000000000000000000000000000000000000000000000000;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; (124 characters)
*
* ...leaving 900 for a message.
*
* I figure we'd want media attachment's at some point
* (common IPFS CID lengths are between 46 [v0] to 55 [v1, base58+sha256]),
* and to give the message format space for extensions,
* let's settle on 800 characters for a message.
*
* So that's:
*
* 60 characters max for a handle
* 800 characters max for a message
*
* *See [Fisherman's Discord post](https://discord.com/channels/925410112368156732/925410112951160879/1190400846547144754))
*/

const sendMessage = async () => {
if (!activeAddress) throw new Error("No active account");
if (
isTopic &&
!isReply &&
(topicName.length === 0 || topicName.length > maxTopicLength)
) {
alert(`Please provide a topic name within ${maxTopicLength} characters.`);
return;
}
if (message.length === 0) {
alert("Type a message before posting");
return;
}
if (message.length > maxMessageLength) {
alert(
`Your message exceeds the maximum length of ${maxMessageLength} characters.`
);
return;
}

setIsSending(true);

try {
let prefix = "";
if (isReply) {
prefix = `r;${replyToTxId}`;
} else if (isTopic && !isReply) {
prefix = `t;${topicName}`;
} else {
prefix = "a;";
}

const note = new Uint8Array(
Buffer.from(`ARC00-0;${prefix};${activeHandle};${message}`)
);

const transactionComposer = new algosdk.AtomicTransactionComposer();
const suggestedParams = await algodClient.getTransactionParams().do();

const transaction = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
from: activeAddress,
to: broadcastChannel.address,
amount: 0,
note,
suggestedParams,
});

transactionComposer.addTransaction({
txn: transaction,
signer: transactionSigner,
});

console.info("Sending message...", transaction);

const result = await transactionComposer.execute(algodClient, 4);

console.info("✅ Successfully sent transaction!", {
confirmedRound: result.confirmedRound,
txIDs: result.txIDs,
});

loadTransactions();

setMessage("");
setTopicName(""); // Reset topic name
} catch (err) {
console.error("Failed to post message", err);
} finally {
setIsSending(false);
onOpenChange(false);
}
};

useEffect(() => {
if (open) {
document.body.classList.add("sheet-open");
} else {
document.body.classList.remove("sheet-open");
}
return () => document.body.classList.remove("sheet-open");
}, [open]);

const quote = useMemo(() => getDescriptionQuote(), []);

return (
<Sheet open={open} onOpenChange={onOpenChange} modal={true}>
<SheetContent side="bottom" className="p-2 pt-4 sm:p-4 md:p-8">
<SheetHeader className="mx-auto grid max-w-[59rem] flex-1 auto-rows-max gap-4 pt-2 pb-6">
<SheetTitle>{`New ${
isReply ? "Reply" : isTopic ? "Topic" : "Conversation"
}`}</SheetTitle>
<SheetDescription className="max-sm:hidden text-muted-foreground italic">
{quote}
</SheetDescription>
</SheetHeader>

{isTopic && !isReply && (
<div className="mx-auto grid max-w-[59rem] flex-1 auto-rows-max gap-4 pb-4">
<Input
id="topicName"
type="text"
placeholder="Enter topic name"
className="p-2 border rounded-md"
value={topicName}
onChange={(evt) => {
const inputTopic = evt.target.value;
if (inputTopic.length <= maxTopicLength) {
setTopicName(inputTopic);
} else {
setTopicName(inputTopic.slice(0, maxTopicLength));
}
}}
/>
{/* <span className="text-xs text-muted-foreground">
{maxTopicLength - topicName.length}/{maxTopicLength}
</span> */}
</div>
)}

<div className="mx-auto grid max-w-[59rem] flex-1 auto-rows-max gap-4">
<form
className="relative overflow-hidden rounded-lg border bg-background focus-within:ring-1 focus-within:ring-ring"
x-chunk=""
>
<Label htmlFor="message" className="sr-only">
Message
</Label>
<Textarea
id="message"
placeholder="Type your message here..."
className="min-h-12 resize-none border-0 p-3 shadow-none focus-visible:ring-0 text-base"
value={message}
onChange={(evt) => {
const inputMessage = evt.target.value;
if (inputMessage.length <= maxMessageLength) {
setMessage(inputMessage);
} else {
setMessage(inputMessage.slice(0, maxMessageLength));
}
}}
/>
<div className="flex items-center justify-between p-3 pt-0">
<span
className="text-xs text-muted-foreground m-2"
style={{
position: "absolute",
bottom: 0,
left: 0,
}}
>
{maxMessageLength - message.length}/{maxMessageLength}
</span>
<Button
type="submit"
size="default"
className="ml-auto gap-1.5"
onClick={(event) => {
event.preventDefault();
sendMessage();
}}
disabled={isSending}
>
{isSending ? (
<>
Posting
<UpdateIcon className="size-3.5 motion-safe:animate-spin-slow" />
</>
) : (
<>
Post
<IconCornerDownLeft className="size-3.5" />
</>
)}
</Button>
</div>
</form>
</div>
</SheetContent>
</Sheet>
);
};

function getDescriptionQuote() {
const index = Math.floor(Math.random() * quotes.length);
return quotes[index];
}

export default Compose;
Loading

0 comments on commit 4e211c8

Please sign in to comment.