Skip to content

Commit

Permalink
Feat/wallet (#7)
Browse files Browse the repository at this point in the history
* Add SAResponse type for Server Actions

* Refactor JSON data component to use button variants

* Add wallet form component and update user page

* Update UI components and add separators
  • Loading branch information
Lodimup authored Feb 17, 2024
1 parent f9c6d16 commit bf4264b
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { Button } from "@/components/ui/button";
import { buttonVariants } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
import { BracesIcon } from "lucide-react";

export const JsonData = ({ data }: { data: object }) => {
return (
<Collapsible>
<CollapsibleTrigger className="flex items-center">
<Button variant={"ghost"} className="w-24">
<BracesIcon size={18} /> JSON
</Button>
<CollapsibleTrigger
className={cn(
"flex items-center w-24",
buttonVariants({ variant: "ghost" })
)}
>
<BracesIcon size={18} /> JSON
</CollapsibleTrigger>
<CollapsibleContent className="bg-slate-100 text-xs font-mono text-slate-600 p-2">
<pre>{JSON.stringify(data, null, 2)}</pre>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use server";

import { transactions } from "@/lib/intra/users";
import { SAResponse } from "@/types/sa-response";
import { IAddAlt } from "./types";
import { revalidateTag } from "next/cache";

/**
* Adds an alternative money
* @param payload - The payload
* @returns A promise that resolves to a SAResponse object with a boolean indicating the success of the operation.
*/
export async function addAlt(payload: IAddAlt): Promise<SAResponse<boolean>> {
try {
const r = await transactions({
...payload,
transactable_type: "Tuteur api",
reason: "cadeau",
});
revalidateTag(payload.login);
return { data: true, error: null };
} catch (e) {
return { data: null, error: "Failed to add Alt" };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";

import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { addAlt } from "./actions";
import { IWalletForm } from "./types";
import { toast } from "sonner";
import { useState } from "react";
const FormSchema = z.object({
value: z.coerce.number().int(),
});

export function WalletForm(props: IWalletForm) {
const [currentValue, setCurrentValue] = useState(props.currentValue);
const [prevValue, setPrevValue] = useState(props.currentValue);
const [isPending, setIsPending] = useState(false);
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
value: 0,
},
});

async function onSubmit(data: z.infer<typeof FormSchema>) {
if (data.value === 0) {
toast.error("Why are you tring to add 0?");
return;
}
setIsPending(true);
setPrevValue(currentValue);
setCurrentValue(currentValue + data.value);
const { data: respData, error } = await addAlt({
value: data.value,
user_id: props.user_id,
login: props.login,
});
if (error) {
setCurrentValue(prevValue);
setIsPending(false);
toast.error(error);
} else if (respData) {
setIsPending(false);
toast.success("Alt added");
form.reset();
}
}

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-1/3 space-y-6">
<FormField
control={form.control}
name="value"
render={({ field }) => (
<FormItem>
<FormLabel>Add, or remove $Alt</FormLabel>
<div className="flex">
<Input
className="rounded-r-none min-w-[100px]"
value={`$Alt ${currentValue}`}
readOnly
/>
<FormControl>
<Input
className="rounded-none min-w-[100px]"
type="number"
step={100}
{...field}
/>
</FormControl>
<Button
className="rounded-l-none"
type="submit"
disabled={isPending}
>
Add
</Button>
</div>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface IAddAlt {
value: number;
user_id: number;
login: string;
}

export interface IWalletForm {
currentValue: number;
user_id: number;
login: string;
}
45 changes: 33 additions & 12 deletions app/app/(dashboard)/dashboard/cadet/[login]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { fetchUser } from "@/lib/intra/users";
import { JsonData } from "./_components/json-data";
import { WalletForm } from "./_components/wallet-form";
import { Separator } from "@/components/ui/separator";
import { CircleUserIcon, MailIcon, UserIcon } from "lucide-react";

export default async function Page({ params }: { params: { login: string } }) {
const { login } = params;
Expand All @@ -10,26 +13,44 @@ export default async function Page({ params }: { params: { login: string } }) {
} catch (e) {
return <div>Not Found</div>;
}
const walletFormProps = {
currentValue: user.wallet,
user_id: user.id,
login: user.login,
};
return (
<main>
<div>
<div className="flex gap-4">
<div>
<Avatar className="w-24 h-24 rounded">
<AvatarImage src={user.image.link} alt={user.login} />
<AvatarFallback className="w-24 h-24 rounded">
{user.login}
</AvatarFallback>
</Avatar>
<Separator className="m-4" />
<div className="flex gap-4">
<div>
<Avatar className="w-24 h-24 rounded">
<AvatarImage src={user.image.link} alt={user.login} />
<AvatarFallback className="w-24 h-24 rounded">
{user.login}
</AvatarFallback>
</Avatar>
</div>
<div>
<div className="flex items-center gap-1">
<UserIcon size={16} />
<p>{user.login}</p>
</div>
<div>
<h2>{user.login}</h2>
<div className="flex items-center gap-1">
<CircleUserIcon size={16} />
<p>{user.displayname}</p>
</div>
<div className="flex items-center gap-1">
<MailIcon size={16} />
<p>{user.email}</p>
</div>
</div>
<JsonData data={user} />
</div>
<Separator className="m-4" />
<div className="flex flex-col gap-4">
<WalletForm {...walletFormProps} />
</div>
<Separator className="m-4" />
<JsonData data={user} />
</main>
);
}
6 changes: 4 additions & 2 deletions app/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Metadata } from "next";
import { Inter as FontSans } from "next/font/google";
import { Toaster } from "@/components/ui/sonner";
import { cn } from "@/lib/utils";
import "@/styles/globals.css";
import type { Metadata } from "next";
import { Inter as FontSans } from "next/font/google";

const fontSans = FontSans({
subsets: ["latin"],
Expand All @@ -26,6 +27,7 @@ export default function RootLayout({
)}
>
{children}
<Toaster />
</body>
</html>
);
Expand Down
7 changes: 7 additions & 0 deletions app/types/sa-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Represents a response object for Server Actions.
* @template T - The type of the data.
*/
export type SAResponse<T> =
| { data: T; error: null }
| { data: null; error: string };

0 comments on commit bf4264b

Please sign in to comment.