Skip to content

Commit

Permalink
feat: Publish post and basic user public page
Browse files Browse the repository at this point in the history
  • Loading branch information
RezaRahemtola committed Jun 28, 2024
1 parent e96f348 commit ead62f7
Show file tree
Hide file tree
Showing 14 changed files with 2,337 additions and 140 deletions.
18 changes: 18 additions & 0 deletions contract/p/gnovox/gnovox.gno
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,27 @@ func UpdateUser(u User, username string) User {
}

func (u *User) Json() *json.Node {
posts := json.ArrayNode("posts", []*json.Node{})
u.Posts.Iterate("", "", func(address string, value interface{}) bool {
p := value.(*Post)
jsonNode := p.Json()
posts.AppendArray(jsonNode)
return false
})

node := json.ObjectNode("", map[string]*json.Node{
"address": json.StringNode("address", u.Address.String()),
"username": json.StringNode("username", u.Username),
"posts": posts,
})
return node
}

func (p *Post) Json() *json.Node {
node := json.ObjectNode("", map[string]*json.Node{
"slug": json.StringNode("slug", p.Slug),
"title": json.StringNode("title", p.Title),
"body": json.StringNode("body", p.Body),
})
return node
}
Expand Down
41 changes: 26 additions & 15 deletions contract/r/gnovox/gnovox.gno
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,62 @@ var (
users avl.Tree // address -> *User
)

func AddUser(username string) error {
func AddUser(username string) {
caller := std.GetOrigCaller()

if _, found := users.Get(caller.String()); found {
// TODO: also check username
return ErrUserExist
panic(ErrUserExist)
}

u := gnovox.NewUser(caller, username)
users.Set(caller.String(), &u)
return nil
}

func UpdateUser(username string) error {
func UpdateUser(username string) {
caller := std.GetOrigCaller()

// TODO: check that username isn't already taken
value, found := users.Get(caller.String());
if found == false {
return ErrUserNotFound
panic(ErrUserNotFound)
}
currentUser := value.(*gnovox.User)
u := gnovox.UpdateUser(*currentUser, username)
users.Set(caller.String(), &u)
return nil
}

func GetUserByAddress(address std.Address) string {
if value, found := users.Get(address.String()); found {
u := value.(*gnovox.User)
jsonResponse := u.Json()
value, found := users.Get(address.String());

if found == false {
panic(ErrUserNotFound)
}
u := value.(*gnovox.User)
jsonResponse := u.Json()

b, err := json.Marshal(jsonResponse)
checkErr(err)

b, err := json.Marshal(jsonResponse)
checkErr(err)
return string(b)
}

func NewPost(slug, title, body string) {
caller := std.GetOrigCaller()

return string(b)
value, found := users.Get(caller.String());
if found == false {
panic(ErrUserNotFound)
}
panic(ErrUserNotFound)
return ""
u := value.(*gnovox.User)
err := u.NewPost(caller, slug, title, body, "") // TODO: Ask pubDate to users
checkErr(err)
}

func GetUsers() string {
jsonResponse := json.ArrayNode("", []*json.Node{})

users.ReverseIterate("", "", func(address string, value interface{}) bool {
users.Iterate("", "", func(address string, value interface{}) bool {
u := value.(*gnovox.User)
jsonNode := u.Json()
jsonResponse.AppendArray(jsonNode)
Expand Down
2 changes: 2 additions & 0 deletions front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"@gnolang/gno-js-client": "^1.2.3",
"@hookform/resolvers": "^3.6.0",
"@mdxeditor/editor": "^3.6.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.2.0",
Expand All @@ -34,6 +35,7 @@
"zustand": "^4.5.2"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.13",
"@tanstack/router-devtools": "^1.40.0",
"@tanstack/router-plugin": "^1.39.13",
"@types/node": "^20.14.8",
Expand Down
33 changes: 14 additions & 19 deletions front/src/components/WalletButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,23 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu.tsx";
import { useProviderStore } from "@/stores/provider.ts";
import { parseGnoEvaluateJsonResponse } from "@/utils";
import { Link, useNavigate } from "@tanstack/react-router";
import { z } from "zod";
import { LogOut, Settings } from "lucide-react";

const userSchema = z.object({
address: z.string(),
username: z.string(),
});
import { LogOut, Settings, User } from "lucide-react";
import { userSchema } from "@/types/user.ts";

const WalletButton: FC = () => {
const { provider } = useProviderStore();
const navigate = useNavigate();

const { toast } = useToast();
const { setAddress, setUsername, setChainID } = useAccountStore();
const { address, setAddress, setUser } = useAccountStore();
const [isLoading, setIsLoading] = useState<boolean>(false);

const [accountInfo, setAccountInfo] = useState<IAccountInfo | null>(null);

const handleWalletConnect = async () => {
setIsLoading(true);

Expand All @@ -49,8 +41,6 @@ const WalletButton: FC = () => {

// Update the account context
setAddress(accountInfo.address);
setAccountInfo(accountInfo);
setChainID(constants.chainID);

try {
const response = await provider.evaluateExpression(
Expand All @@ -59,7 +49,7 @@ const WalletButton: FC = () => {
);
const jsonResponse = parseGnoEvaluateJsonResponse(response);
const parsedResponse = userSchema.parse(jsonResponse);
setUsername(parsedResponse.username);
setUser(parsedResponse);
} catch (error) {
await navigate({ to: "/signup" });
}
Expand All @@ -83,13 +73,12 @@ const WalletButton: FC = () => {

const onLogout = () => {
setAddress(null);
setAccountInfo(null);
setChainID(null);
setUser(null);
};

return (
<>
{accountInfo === null ? (
{address === null ? (
<Button onClick={handleWalletConnect} disabled={isLoading}>
Connect wallet
</Button>
Expand All @@ -107,14 +96,20 @@ const WalletButton: FC = () => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
{/*TODO: Use username here*/}
<Link to={`/user/${address}`} className="flex gap-1">
<User />
<span className="my-auto">Profile</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem>
<Link to="/settings" className="flex gap-1">
<Settings />
<span className="my-auto">Settings</span>
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onLogout} className="cursor-pointer gap-1">
<LogOut />
<span className="my-auto">Logout</span>
Expand Down
18 changes: 18 additions & 0 deletions front/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ import { Route as rootRoute } from "./routes/__root";

// Create Virtual Routes

const WriteLazyImport = createFileRoute("/write")();
const SignupLazyImport = createFileRoute("/signup")();
const SettingsLazyImport = createFileRoute("/settings")();
const IndexLazyImport = createFileRoute("/")();
const UserUsernameLazyImport = createFileRoute("/user/$username")();

// Create/Update Routes

const WriteLazyRoute = WriteLazyImport.update({
path: "/write",
getParentRoute: () => rootRoute,
} as any).lazy(() => import("./routes/write.lazy").then((d) => d.Route));

const SignupLazyRoute = SignupLazyImport.update({
path: "/signup",
getParentRoute: () => rootRoute,
Expand Down Expand Up @@ -68,6 +74,13 @@ declare module "@tanstack/react-router" {
preLoaderRoute: typeof SignupLazyImport;
parentRoute: typeof rootRoute;
};
"/write": {
id: "/write";
path: "/write";
fullPath: "/write";
preLoaderRoute: typeof WriteLazyImport;
parentRoute: typeof rootRoute;
};
"/user/$username": {
id: "/user/$username";
path: "/user/$username";
Expand All @@ -84,6 +97,7 @@ export const routeTree = rootRoute.addChildren({
IndexLazyRoute,
SettingsLazyRoute,
SignupLazyRoute,
WriteLazyRoute,
UserUsernameLazyRoute,
});

Expand All @@ -98,6 +112,7 @@ export const routeTree = rootRoute.addChildren({
"/",
"/settings",
"/signup",
"/write",
"/user/$username"
]
},
Expand All @@ -110,6 +125,9 @@ export const routeTree = rootRoute.addChildren({
"/signup": {
"filePath": "signup.lazy.tsx"
},
"/write": {
"filePath": "write.lazy.tsx"
},
"/user/$username": {
"filePath": "user/$username.lazy.tsx"
}
Expand Down
10 changes: 6 additions & 4 deletions front/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ const RootLayout = () => {
</Link>
</div>
<div className="flex items-center gap-4">
<Button variant="ghost" className="gap-2" disabled={address === null}>
<SquarePen />
Write
</Button>
<Link to="/write">
<Button variant="ghost" className="gap-2" disabled={address === null}>
<SquarePen />
Write
</Button>
</Link>

<WalletButton />
</div>
Expand Down
10 changes: 6 additions & 4 deletions front/src/routes/settings.lazy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@ const profileFormSchema = z.object({
type ProfileFormSchema = z.infer<typeof profileFormSchema>;

const Settings = () => {
const { address, username, setUsername } = useAccountStore();
const { address, user, setUser } = useAccountStore();

const form = useForm<ProfileFormSchema>({
resolver: zodResolver(profileFormSchema),
defaultValues: {
username: username ?? undefined,
username: user?.username ?? undefined,
},
});

const onSubmit = async (values: ProfileFormSchema) => {
if (user === null) return;

await AdenaService.sendTransaction(
[
{
Expand All @@ -43,7 +45,7 @@ const Settings = () => {
],
5000000,
);
setUsername(values.username);
setUser({ ...user, username: values.username });
};

return (
Expand All @@ -57,7 +59,7 @@ const Settings = () => {
<span className="font-semibold text-primary">General</span>
</nav>
<div className="grid gap-6">
<Card x-chunk="dashboard-04-chunk-1">
<Card>
<CardHeader>
<CardTitle>Profile</CardTitle>
<CardDescription>Update your information</CardDescription>
Expand Down
45 changes: 44 additions & 1 deletion front/src/routes/user/$username.lazy.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,50 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import "@mdxeditor/editor/style.css";
import { useEffect, useState } from "react";
import { constants } from "@/constants.ts";
import { parseGnoEvaluateJsonResponse } from "@/utils";
import { useProviderStore } from "@/stores/provider.ts";
import { useToast } from "@/components/ui/use-toast.ts";
import { User, userSchema } from "@/types/user.ts";

const UserPage = () => {
return <div>Hello /user/$userId!</div>;
const { provider } = useProviderStore();
const { toast } = useToast();
const { username } = Route.useParams();
const [pageUser, setPageUser] = useState<User | null>(null);

useEffect(() => {
(async () => {
try {
// TODO: Get by username instead
const response = await provider.evaluateExpression(constants.realmPath, `GetUserByAddress("${username}")`);
const jsonResponse = parseGnoEvaluateJsonResponse(response);
const parsedResponse = userSchema.parse(jsonResponse);
setPageUser(parsedResponse);
} catch (error) {
// TODO: redirect to 404
toast({
title: "User not found",
variant: "destructive",
});
}
})();
}, []);

if (pageUser !== null) {
return (
<>
<p>Address: {pageUser.address}</p>
<p>Username: {pageUser.username}</p>
{pageUser.posts.map((post) => (
<div key={post.slug}>
<p>{post.title}</p>
<p>{post.slug}</p>
</div>
))}
</>
);
}
};

export const Route = createLazyFileRoute("/user/$username")({
Expand Down
Loading

0 comments on commit ead62f7

Please sign in to comment.