Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 187 additions & 31 deletions src/components/dashboard/members/AddMember.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"use client"

import React, { useMemo, useState } from 'react';
import {
Dialog,
DialogClose,
Expand All @@ -10,69 +13,205 @@ import {
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { Plus } from 'lucide-react';
import { Plus, Loader2 } from 'lucide-react';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { useAccount, useContract, useReadContract, useSendTransaction } from '@starknet-react/core';
import { FACTORYABI } from '@/lib/abi/factory-abi';
import { LITTLEFINGER_FACTORY_ADDRESS } from '@/lib/constants';
import { contractAddressToHex } from '@/lib/utils';
import { COREABI } from '@/lib/abi/core-abi';

const AddMember = () => {
const [isOpen, setIsOpen] = useState(false);
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
alias: '',
role: '',
walletAddress: ''
});

const { address: user } = useAccount();

const { data: contractAddresses } = useReadContract({
abi: FACTORYABI,
address: LITTLEFINGER_FACTORY_ADDRESS,
functionName: "get_vault_org_pair",
args: [user!]
});

const orgAddress = contractAddresses ? contractAddressToHex(contractAddresses[0]) : undefined;

const { contract } = useContract({
abi: COREABI,
address: orgAddress,
});

const calls = useMemo(() => {
const inputIsValid =
formData.firstName !== "" &&
formData.lastName !== "" &&
formData.alias !== "" &&
formData.role !== "" &&
formData.walletAddress !== "" &&
contract;

if (!inputIsValid) return undefined;

let roleValue: number;
switch (formData.role) {
case 'admin':
roleValue = 0;
break;
case 'member':
roleValue = 1;
break;
case 'viewer':
roleValue = 2;
break;
default:
roleValue = 1;
}

try {
return [
contract.populate("add_member", [
formData.firstName,
formData.lastName,
formData.alias,
roleValue,
formData.walletAddress
])
];
} catch (error) {
console.error('Error preparing add member transaction:', error);
return undefined;
}
}, [formData, contract]);

const {
sendAsync,
isPending
} = useSendTransaction({
calls
});

const handleInputChange = (field: string, value: string) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

if (!calls) return;

try {
await sendAsync();

setFormData({
firstName: '',
lastName: '',
alias: '',
role: '',
walletAddress: ''
});
setIsOpen(false);

} catch (error) {
console.error('Add member transaction failed:', error);
}
};

const handleCancel = () => {
setFormData({
firstName: '',
lastName: '',
alias: '',
role: '',
walletAddress: ''
});
setIsOpen(false);
};

return (
<Dialog>
<form>
<DialogTrigger asChild>
<Button
className="bg-regal-gold hover:bg-regal-gold/60 rounded-[0.5rem] text-white font-normal text-sm sm:text-base"
variant={'outline'}
>
<Plus size={24} /> Add Members
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] bg-regal-black border border-white/35 sm:rounded-xl">
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button
className="bg-regal-gold hover:bg-regal-gold/60 rounded-[0.5rem] text-white font-normal text-sm sm:text-base px-3 py-2 sm:px-4 sm:py-2"
variant={'outline'}
>
<Plus size={20} className="sm:size-6" />
<span className="ml-1 sm:ml-2">Add Members</span>
</Button>
</DialogTrigger>
<DialogContent className="w-[95vw] sm:max-w-[425px] bg-regal-black border border-white/35 sm:rounded-xl mx-auto">
<form onSubmit={handleSubmit}>
<DialogHeader>
<DialogTitle>Add Member</DialogTitle>
<DialogTitle className="text-white mb-4">Add Member</DialogTitle>
</DialogHeader>
<div className="grid gap-4">
<div className="grid gap-3">
<Label htmlFor="name-1">First Name</Label>
<div className="grid gap-4 sm:gap-5 max-h-[60vh] sm:max-h-[65vh] overflow-y-auto pr-2">
<div className="grid gap-2 sm:gap-3">
<Label htmlFor="name-1" className="text-white">First Name</Label>
<Input
id="name-1"
name="first_name"
value={formData.firstName}
onChange={(e) => handleInputChange('firstName', e.target.value)}
className="bg-transparent border border-white/25 rounded-xl text-white placeholder:text-gray-400 focus:border-white/50"
disabled={isPending}
/>
</div>
<div className="grid gap-3">
<Label htmlFor="name-2">Last Name</Label>
<div className="grid gap-2 sm:gap-3">
<Label htmlFor="name-2" className="text-white">Last Name</Label>
<Input
id="name-2"
name="last_name"
value={formData.lastName}
onChange={(e) => handleInputChange('lastName', e.target.value)}
className="bg-transparent border border-white/25 rounded-xl text-white placeholder:text-gray-400 focus:border-white/50"
disabled={isPending}
/>
</div>
<div className="grid gap-3">
<Label htmlFor="username-1">Alias/Username</Label>
<div className="grid gap-2 sm:gap-3">
<Label htmlFor="username-1" className="text-white">Alias/Username</Label>
<Input
id="username-1"
name="username"
value={formData.alias}
onChange={(e) => handleInputChange('alias', e.target.value)}
className="bg-transparent border border-white/25 rounded-xl text-white placeholder:text-gray-400 focus:border-white/50"
disabled={isPending}
/>
</div>
<div className="grid gap-3">
<Label htmlFor="wallet-address">Wallet Address</Label>
<div className="grid gap-2 sm:gap-3">
<Label htmlFor="wallet-address" className="text-white">Wallet Address</Label>
<Input
id="wallet-address"
name="wallet_address"
className="bg-transparent border border-white/25 rounded-xl text-white placeholder:text-gray-400 focus:border-white/50"
value={formData.walletAddress}
onChange={(e) => handleInputChange('walletAddress', e.target.value)}
className="bg-transparent border border-white/25 rounded-xl text-white placeholder:text-gray-400 focus:border-white/50 font-mono text-xs sm:text-sm"
disabled={isPending}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="role-1" className="text-white text-sm">
Role*
</Label>
<Select>
<Select
value={formData.role}
onValueChange={(value: string) => handleInputChange('role', value)}
disabled={isPending}
>
<SelectTrigger className="bg-transparent border border-white/25 rounded-xl text-white focus:border-white/50">
<SelectValue
placeholder="Select a role"
Expand Down Expand Up @@ -102,20 +241,37 @@ const AddMember = () => {
</Select>
</div>
</div>
<DialogFooter className="flex-row gap-3">
<DialogFooter className="flex flex-row gap-x-4 pt-4">
<DialogClose asChild>
<Button className="flex-1 rounded-[0.5rem]" variant="outline">
<Button
className="flex-1 rounded-[0.5rem] border-white/25 text-white hover:bg-white/10"
variant="outline"
type="button"
onClick={handleCancel}
disabled={isPending}
>
Cancel
</Button>
</DialogClose>
<Button className="flex-1 rounded-[0.5rem] bg-regal-brown" type="submit">
Save changes
<Button
className="flex-1 rounded-[0.5rem] bg-regal-brown hover:bg-regal-brown/80 text-white disabled:opacity-50"
type="submit"
disabled={isPending || !calls}
>
{isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Adding...
</>
) : (
'Add Member'
)}
</Button>
</DialogFooter>
</DialogContent>
</form>
</form>
</DialogContent>
</Dialog>
);
};

export default AddMember;
export default AddMember;
Loading