From 74e074728a7a90b5a2760f3b23b10b633556ea02 Mon Sep 17 00:00:00 2001 From: Trevor <107587461+funkornaut001@users.noreply.github.com> Date: Sat, 9 Dec 2023 10:54:59 -0600 Subject: [PATCH] wagmi hooks, lots of notes, working hooks/login flow, edits to isOwner (#23) * wagmi hooks, lots of notes * isOwner update * changes to get login flow to check for isOwner and isEmployee - working * fix bug in isOwner --------- Co-authored-by: Spencer Schoeneman --- NOTES.md | 21 + packages/hardhat/contracts/Payroll.sol | 1194 +++++++++-------- .../dash-wind/features/employees/index.tsx | 66 +- .../dash-wind/features/payments/index.tsx | 51 + .../settings/profilesettings/index.tsx | 22 + .../dash-wind/features/user/Login.tsx | 192 ++- .../web-3-crew/hooks/useDebounce.ts | 15 + .../web-3-crew/register-page/form.tsx | 73 - packages/nextjs/pages/index.tsx | 10 +- 9 files changed, 962 insertions(+), 682 deletions(-) create mode 100644 packages/nextjs/components/web-3-crew/hooks/useDebounce.ts delete mode 100644 packages/nextjs/components/web-3-crew/register-page/form.tsx diff --git a/NOTES.md b/NOTES.md index 3ad5d59..1ee5fde 100644 --- a/NOTES.md +++ b/NOTES.md @@ -31,3 +31,24 @@ DeployForm - just using useContractWrite was able to click button and make action got this error in browser console next-dev.js:20 ConnectorNotFoundError: Connector not found at writeContract (chunk-TSH6VVF4.js:2348:1) +## 12/8 + +deployed payroll 0x689946c87d38c99dc516c4ba4639023e247bf6ea +12/9 pr - 0xb1476e9c2e452ec455b6a64a05e416007639678b +deployed TT 0x6898d690e7060465aa754528a51e645745921cc5 +12/9 tt - 0xc0ea07f3a200978297ba44107c851150e701044e +from google login 0x1aD394b0c57dbC57f16A5A54b4ccee436b678287 + +List of polygon mumbai address all in my MM wallet (dev) +acct 2 0x73672fe2cf2F3cD99865fE6F62fF34Ac011F7EB7 +acct 3 0x7F2313624e0d64d90954d4bcc7Af334Df124CFCe +acct 6 0x300AE201869d43b9979aFe350d458E4bDD146526 + +added notes and pseudo code/wagmi hooks for: +- Employee to change wallet, and set their payment splits in dash-wind/features/settings/profilesettings/index +- Multiple features( is employee, add employee, grab salary/hourly employees) in dash-wind/features/employees/index +- Added hooks for all pay employee functions in dash-wind/features/payments + +## 12/9 +Deployed new factory addr 0x9C5EE054F0798fd0C36884f53901256Efb9294C6 +Needed to udate isOwner func to return a bool. previsouly i was calling owner() which returned an address. \ No newline at end of file diff --git a/packages/hardhat/contracts/Payroll.sol b/packages/hardhat/contracts/Payroll.sol index b4df6d1..f74d1c0 100644 --- a/packages/hardhat/contracts/Payroll.sol +++ b/packages/hardhat/contracts/Payroll.sol @@ -2,11 +2,11 @@ pragma solidity 0.8.19; // imports -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; //"@openzeppelin/access/Ownable.sol"; -- changed to slither -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; //"@openzeppelin/token/ERC20/IERC20.sol"; -- changed to slither -import {ITokenTransferor} from "./interfaces/ITokenTransferor.sol"; +import { ITokenTransferor } from "./interfaces/ITokenTransferor.sol"; /** * @author Funkornaut @@ -14,537 +14,665 @@ import {ITokenTransferor} from "./interfaces/ITokenTransferor.sol"; * Employees are able to select which evm chain & tokens they wish to be paid on. * Assumptions: * Original Employee address/wallet is created by Employer admin and the employeeId is created from that wallet address - * We only pay Employees in BnMTokens (therortically this will be a stablecoin) + * We only pay Employees in BnMTokens (therortically this will be a stablecoin) */ - contract Payroll is Ownable { - -////////////// -/// errors /// -////////////// -error PercentageMustEqual100(); -error InvalidAddress(); -error EmployeeAlreadyExists(); -error EmployeeDoesNotExists(); -error EmployeeIsNotSalary(); -error EmployeeIsNotHourly(); -error HoursWorkedArrayDoesNotMatchEmployees(uint8[]); -error OwnerCantBeEmployee(); -error MustHavePayRate(); -// interfaces, libraries, contracts - -///@dev Burn And Mint Test Token assume this is the stablecoin the Employer has choosen to pay its Employees in -/// mumbai address 0xf1E3A5842EeEF51F2967b3F05D45DD4f4205FF40 -IERC20 public bnmToken; -//ICCIPTokenSender public ccip; -///@dev address of TokenTransferor Deployed with Payroll -ITokenTransferor public ccip; - -// Automation Address -//address public automationAddress; - -// Type declarations - -// State variables - - // eth sepolia - uint64 public immutable i_destinationChainIdEth = 16015286601757825753; - // OP goerli - uint64 public immutable i_destinationChainIDdOP = 2664363617261496610; - // Avax Fuji - uint64 public immutable i_destinationChainIdAvax = 14767482510784806043; - // BNB Testnet - uint64 public immutable i_destinationChainIdBnb = 13264668187771770619; - - struct Employee { - // Unique identifier for the employee - hash of the originally assigned wallet and block.timestamp - bytes32 employeeId; - // Primary wallet address of the employee, could be used on any chain, is updateable by employee or employeer - address primaryWallet; - bool isSalary; - //bool localChainPayment; - uint256 payRate; - // the split information for each chain - PaymentSplit paymentSplits; - } - - ///@dev Mapping of employeeId to Employee struct - mapping (bytes32 => Employee) public employeeIds; - - ///@dev mapping of employee primary address to Employee struct - mapping (address => Employee) public employees; - - struct PaymentSplit { - // The percentage of pay to be sent to those chains - uint8 paySplitPercentageNative; - uint8 paySplitPercentage1; //ETH - uint8 paySplitPercentage2; //OP - uint8 paySplitPercentage3; //Avax - uint8 paySplitPercentage4; //BNB - } - - // mapping of employeeId to PaymentSplit struct -- might not be necessary - mapping (bytes32 => PaymentSplit) public paymentSplits; - - // array of all salaried employees - address[] public salariedEmployees; - - // mapping of address to index in salariedEmployees array - mapping (address => uint256) public salariedEmployeeIndex; - - // array of all hourly employees - address[] public hourlyEmployees; - - // mapping of address to index in hourlyEmployees array - mapping (address => uint256) public hourlyEmployeeIndex; - - ////////////// - /// Events /// - ////////////// - //@todo do we need parameters/vales? - event SingleEmployeePaid(address _employeeAddress, uint256 _amount); - event BalanceWithdrawn(uint256 amount); - event AllSallaryEmployeesPaid(); - event AllHourlyEmployeesPaid(); - event AllEmployeesPaid(); - event EmployeeAdded(address _employeeAddress); - event SalarySet(address _employeeAddress); - event EmployeeFired(address _employeeAddress); - event WalletAddressChanged(address _oldAddress, address _newAddress); - event PaymentSplitSet(address _employeeAddress, uint8[5] _paySplitPercentages); - event TransferToChain(address _receiver, uint64 _destinationChainSelector, bytes32 messageId); - - ///////////////// - /// Modifiers /// - ///////////////// - modifier onlyEmployee() { - require(employees[msg.sender].employeeId != 0, "Only employees can call this function"); - _; - } - - modifier onlyEmployeeOrOwner() { - require(employees[msg.sender].employeeId != 0 || msg.sender == owner(), "Only employees or owner can call this function"); - _; - } - - // modifier onlyAutomationOrOwner() { - // require(msg.sender == automationAddress || msg.sender == owner(), "Only automation or owner can call this function"); - // _; - // } -// Functions - -// Layout of Functions: -// constructor - // @todo maybe make a constructor params struct that can set all chainIds, CCIPcontract, token whitelist? - constructor(address _ccipTokenTranferor, address _bnmToken) { - ccip = ITokenTransferor(_ccipTokenTranferor); - bnmToken = IERC20(_bnmToken); - } - - function recieve() external payable {} - - ///@dev withdraw function incase any ether/native token is sent to contract mistakenly - function withdraw(address payable _to) external onlyOwner { - if (_to == address(0)) { - revert InvalidAddress(); - } - uint256 amount = address(this).balance; - (bool sent, ) = _to.call{value: amount}(""); - require(sent, "Failed to send Ether"); - - emit BalanceWithdrawn(amount); - } - - function transferOwnership(address newOwner) public override onlyOwner { - require(newOwner != address(0), "Ownable: new owner is the zero address"); - _transferOwnership(newOwner); - } - - -// external -// public -// internal -// private -// internal & private view & pure functions -// external & public view & pure functions - - ////////////////////////////// - /// Pay Employee Functions /// - ////////////////////////////// - - function paySingleEmployee(address _employeeAddress, uint8 _hoursWorked) external onlyOwner { - //"Invalid address" - if (_employeeAddress == address(0)) { - revert InvalidAddress(); - } - - //"Employee does not exist" - if (employees[_employeeAddress].employeeId == 0) { - revert EmployeeDoesNotExists(); - } - - //@todo can this be memory? - //Employee memory employee = employees[_employeeAddress]; - - uint256 weeklyPay = employees[_employeeAddress].isSalary - ? _calculateWeeklyPaymentSalary(_employeeAddress) - : _calculateWeeklyPaymentHourly(_employeeAddress, _hoursWorked); - - PaymentSplit memory splits = employees[_employeeAddress].paymentSplits; - - - // Transfer tokens to each chain according to the payment split - if (splits.paySplitPercentageNative !=0) { - bool success = bnmToken.transfer(_employeeAddress, ((weeklyPay * splits.paySplitPercentageNative) / 100)); - require(success, "Token transfer failed"); - } - if (splits.paySplitPercentage1 != 0) { - _transferToChain(i_destinationChainIdEth, _employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage1) / 100)); - } - if (splits.paySplitPercentage2 != 0) { - _transferToChain(i_destinationChainIDdOP, _employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage2) / 100)); - } - if (splits.paySplitPercentage3 != 0) { - _transferToChain(i_destinationChainIdAvax, _employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage3) / 100)); - } - if (splits.paySplitPercentage4 != 0) { - _transferToChain(i_destinationChainIdBnb, _employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage4) / 100)); - } - //ccip.transferTokens(, _employeeAddress, , weeklyPay) - - emit SingleEmployeePaid(_employeeAddress, weeklyPay); - } - - function payAllSallaryEmployees() public onlyOwner { - for(uint i = 0; i < salariedEmployees.length; ++i) { - - address employeeAddress = salariedEmployees[i]; - - uint256 weeklyPay = _calculateWeeklyPaymentSalary(employeeAddress); - - PaymentSplit memory splits = employees[employeeAddress].paymentSplits; - - - // Transfer tokens to each chain according to the payment split - if (splits.paySplitPercentageNative !=0) { - bool success = bnmToken.transfer(employeeAddress, ((weeklyPay * splits.paySplitPercentageNative) / 100)); - require(success, "Token transfer failed"); - } - if (splits.paySplitPercentage1 != 0) { - _transferToChain(i_destinationChainIdEth, employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage1) / 100)); - } - if (splits.paySplitPercentage2 != 0) { - _transferToChain(i_destinationChainIDdOP, employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage2) / 100)); - } - if (splits.paySplitPercentage3 != 0) { - _transferToChain(i_destinationChainIdAvax, employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage3) / 100)); - } - if (splits.paySplitPercentage4 != 0) { - _transferToChain(i_destinationChainIdBnb, employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage4) / 100)); - } - - } - - emit AllSallaryEmployeesPaid(); - } - - function payAllHourlyEmployees(uint8[] calldata _hoursWorked) public onlyOwner { - if (hourlyEmployees.length != _hoursWorked.length){ - revert HoursWorkedArrayDoesNotMatchEmployees(_hoursWorked); - } - - for(uint i = 0; i < hourlyEmployees.length; ++i){ - - address employeeAddress = hourlyEmployees[i]; - - uint256 weeklyPay = _calculateWeeklyPaymentHourly(employeeAddress, _hoursWorked[i]); - - PaymentSplit memory splits = employees[employeeAddress].paymentSplits; - - // Transfer tokens to each chain according to the payment split - if (splits.paySplitPercentageNative !=0) { - bool success = bnmToken.transfer(employeeAddress, ((weeklyPay * splits.paySplitPercentageNative) / 100)); - require(success, "Token transfer failed"); - } - if (splits.paySplitPercentage1 != 0) { - _transferToChain(i_destinationChainIdEth, employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage1) / 100)); - } - if (splits.paySplitPercentage2 != 0) { - _transferToChain(i_destinationChainIDdOP, employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage2) / 100)); - } - if (splits.paySplitPercentage3 != 0) { - _transferToChain(i_destinationChainIdAvax, employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage3) / 100)); - } - if (splits.paySplitPercentage4 != 0) { - _transferToChain(i_destinationChainIdBnb, employeeAddress, address(bnmToken), ((weeklyPay * splits.paySplitPercentage4) / 100)); - } - } - - emit AllHourlyEmployeesPaid(); - - } - - function payAllEmployees(uint8[] calldata _hoursWorked) external onlyOwner { - payAllSallaryEmployees(); - payAllHourlyEmployees(_hoursWorked); - - emit AllEmployeesPaid(); - } - - function _transferToChain(uint64 _destinationChainSelector, address _receiver, address _token, uint256 _amount) internal returns (bytes32 messageId) { - bnmToken.approve(address(ccip), _amount); - - bool success = bnmToken.transfer(address(ccip), _amount); - require(success, "Token transfer failed"); - - messageId = ccip.transferTokensPayNative(_destinationChainSelector, _receiver, _token, _amount); - emit TransferToChain(_receiver, _destinationChainSelector, messageId); - - } - - - /////////////////////////////////////////////// - /// Only Owner Employee Managment Functions /// - /////////////////////////////////////////////// - - ///@dev if employee _isSalary = true then _payRate is their annual salary. - ///@dev if employee _isSalary = false then _payRate is their hourly rate. - function addEmployee(address _employeeAddress, bool _isSalary, uint256 _payRate) external onlyOwner { - //"Invalid address" - if (_employeeAddress == address(0)) { - revert InvalidAddress(); - } - //"Employee already exists" - if (employees[_employeeAddress].employeeId != 0) { - revert EmployeeAlreadyExists(); - } - - if (_employeeAddress == owner()) { - revert OwnerCantBeEmployee(); - } - - if (_payRate == 0) { - revert MustHavePayRate(); - } - - // Generate a unique ID for the new employee - // Employee address has been created by Employer w/ account abstraction - bytes32 employeeId = keccak256(abi.encodePacked(_employeeAddress, block.timestamp)); - - Employee memory newEmployee = Employee({ - employeeId: employeeId, - primaryWallet: _employeeAddress, - isSalary: _isSalary, - payRate: _payRate, - paymentSplits: PaymentSplit({ - paySplitPercentageNative: 100, //mumbai - paySplitPercentage1: 0, // eth - paySplitPercentage2: 0, // op - paySplitPercentage3: 0, // avax - paySplitPercentage4: 0 // bnb - }) - }); - - // update employees address mapping - employees[_employeeAddress] = newEmployee; - // update employeeId mapping - employeeIds[employeeId] = newEmployee; - - // update salary/hourly array and index mapping - if (_isSalary == true){ - salariedEmployees.push(_employeeAddress); - uint256 index = salariedEmployees.length - 1; - salariedEmployeeIndex[_employeeAddress] = index; - } else { - hourlyEmployees.push(_employeeAddress); - uint256 index = hourlyEmployees.length - 1; - hourlyEmployeeIndex[_employeeAddress] = index; - } - - emit EmployeeAdded(_employeeAddress); - } - - ///@dev if employee _isSalary = true then _payRate is their annual salary. - ///@dev if employee _isSalary = false then _payRate is their hourly rate. - function setEmployeeSalary(address _employeeAddress, bool _isSalary, uint256 _payRate) external onlyOwner { - //"Invalid address" - if (_employeeAddress == address(0)) { - revert InvalidAddress(); - } - //"Employee does not exist" - if (employees[_employeeAddress].employeeId == 0) { - revert EmployeeDoesNotExists(); - } - - employees[_employeeAddress].isSalary = _isSalary; - employees[_employeeAddress].payRate = _payRate; - - emit SalarySet(_employeeAddress); - } - - function removeEmployee(address _employeeAddress) external onlyOwner { - // might not be necessary - if (employees[_employeeAddress].employeeId == 0) { - revert EmployeeDoesNotExists(); - } - - Employee storage employee = employees[_employeeAddress]; - - if (employee.isSalary == true) { - uint256 index = salariedEmployeeIndex[_employeeAddress]; - _removeFromArray(index, salariedEmployees); - } else { - uint256 index = hourlyEmployeeIndex[_employeeAddress]; - _removeFromArray(index, hourlyEmployees); - } - - //@todo do I need to update the mapping or address to employee struct too? see above - delete employees[_employeeAddress]; - - emit EmployeeFired(_employeeAddress); - } - - ////////////////////////////////////////////////////////// - /// Employee & Owner Only Emloyee Management Functions /// - ////////////////////////////////////////////////////////// - - function changePrimaryWalletAddress(address _oldAddress, address _newAddress) public onlyEmployee { - //"Invalid address" - if (_newAddress == address(0)) { - revert InvalidAddress(); - } - //"Employee does not exist" -- @todo don't think I need check because of onlyEmployee modifier - if (employees[_oldAddress].employeeId == 0) { - revert EmployeeDoesNotExists(); - } - - // Copy to memory the employee - Employee memory updatedEmployee = employees[_oldAddress]; - - //@todo might need to update the mapping as well or use the employeeId for the mapping key - updatedEmployee.primaryWallet = _newAddress; - - if (employees[_oldAddress].isSalary == true) { - // remove old wallet from appropriate array - uint256 oldIndex = salariedEmployeeIndex[_oldAddress]; - _removeFromArray(oldIndex, salariedEmployees); - - // add new wallet to appropriate array & update index mapping - salariedEmployees.push(_newAddress); - uint256 newIndex = salariedEmployees.length - 1; - salariedEmployeeIndex[_newAddress] = newIndex; - } else { - // remove old wallet from appropriate array - uint256 oldIndex = hourlyEmployeeIndex[_oldAddress]; - _removeFromArray(oldIndex, hourlyEmployees); - - // add new wallet to appropriate array & update index mapping - hourlyEmployees.push(_newAddress); - uint256 newIndex = hourlyEmployees.length - 1; - hourlyEmployeeIndex[_newAddress] = newIndex; - } - - //delete old address mapping -- do I need to delete and re-add? - delete employees[_oldAddress]; - - //update employee address mapping - employees[_newAddress] = updatedEmployee; - - - emit WalletAddressChanged(_oldAddress, _newAddress); - - } - - //@todo does _paySplit need to be callData? - function setPaymentSplits(address _employeeAddress, uint8[5] calldata _paySplitPercentages) public onlyEmployeeOrOwner { - uint8 totalPercentage = 0; - - for (uint i = 0; i < _paySplitPercentages.length; ++i) { - totalPercentage += _paySplitPercentages[i]; - } - - if (totalPercentage != 100) {revert PercentageMustEqual100();} - - PaymentSplit memory newSplit = PaymentSplit({ - //@todo get the chainIds out of this struct - paySplitPercentageNative: _paySplitPercentages[0], // mumbai - paySplitPercentage1: _paySplitPercentages[1], // eth - paySplitPercentage2: _paySplitPercentages[2], // op - paySplitPercentage3: _paySplitPercentages[3], // avax - paySplitPercentage4: _paySplitPercentages[4] // bnb - }); - - Employee storage employee = employees[_employeeAddress]; - employee.paymentSplits = newSplit; - - emit PaymentSplitSet(_employeeAddress, _paySplitPercentages); - } - - // function setAutomationAddress(address _automationAddress) external onlyOwner { - // automationAddress = _automationAddress; - // } - //////////////////// - /// Helper Funcs /// - //////////////////// - - function _removeFromArray(uint256 _index, address[] storage array) internal { - if (_index >= array.length) return; - - for (uint i = _index; i Employee) public employeeIds; + + ///@dev mapping of employee primary address to Employee struct + mapping(address => Employee) public employees; + + struct PaymentSplit { + // The percentage of pay to be sent to those chains + uint8 paySplitPercentageNative; + uint8 paySplitPercentage1; //ETH + uint8 paySplitPercentage2; //OP + uint8 paySplitPercentage3; //Avax + uint8 paySplitPercentage4; //BNB + } + + // mapping of employeeId to PaymentSplit struct -- might not be necessary + mapping(bytes32 => PaymentSplit) public paymentSplits; + + // array of all salaried employees + address[] public salariedEmployees; + + // mapping of address to index in salariedEmployees array + mapping(address => uint256) public salariedEmployeeIndex; + + // array of all hourly employees + address[] public hourlyEmployees; + + // mapping of address to index in hourlyEmployees array + mapping(address => uint256) public hourlyEmployeeIndex; + + ////////////// + /// Events /// + ////////////// + //@todo do we need parameters/vales? + event SingleEmployeePaid(address _employeeAddress, uint256 _amount); + event BalanceWithdrawn(uint256 amount); + event AllSallaryEmployeesPaid(); + event AllHourlyEmployeesPaid(); + event AllEmployeesPaid(); + event EmployeeAdded(address _employeeAddress); + event SalarySet(address _employeeAddress); + event EmployeeFired(address _employeeAddress); + event WalletAddressChanged(address _oldAddress, address _newAddress); + event PaymentSplitSet( + address _employeeAddress, + uint8[5] _paySplitPercentages + ); + event TransferToChain( + address _receiver, + uint64 _destinationChainSelector, + bytes32 messageId + ); + + ///////////////// + /// Modifiers /// + ///////////////// + modifier onlyEmployee() { + require( + employees[msg.sender].employeeId != 0, + "Only employees can call this function" + ); + _; + } + + modifier onlyEmployeeOrOwner() { + require( + employees[msg.sender].employeeId != 0 || msg.sender == owner(), + "Only employees or owner can call this function" + ); + _; + } + + // modifier onlyAutomationOrOwner() { + // require(msg.sender == automationAddress || msg.sender == owner(), "Only automation or owner can call this function"); + // _; + // } + // Functions + + // Layout of Functions: + // constructor + // @todo maybe make a constructor params struct that can set all chainIds, CCIPcontract, token whitelist? + constructor(address _ccipTokenTranferor, address _bnmToken) { + ccip = ITokenTransferor(_ccipTokenTranferor); + bnmToken = IERC20(_bnmToken); + } + + function recieve() external payable {} + + ///@dev withdraw function incase any ether/native token is sent to contract mistakenly + function withdraw(address payable _to) external onlyOwner { + if (_to == address(0)) { + revert InvalidAddress(); + } + uint256 amount = address(this).balance; + (bool sent, ) = _to.call{ value: amount }(""); + require(sent, "Failed to send Ether"); + + emit BalanceWithdrawn(amount); + } + + function transferOwnership(address newOwner) public override onlyOwner { + require( + newOwner != address(0), + "Ownable: new owner is the zero address" + ); + _transferOwnership(newOwner); + } + + // external + // public + // internal + // private + // internal & private view & pure functions + // external & public view & pure functions + + ////////////////////////////// + /// Pay Employee Functions /// + ////////////////////////////// + + function paySingleEmployee( + address _employeeAddress, + uint8 _hoursWorked + ) external onlyOwner { + //"Invalid address" + if (_employeeAddress == address(0)) { + revert InvalidAddress(); + } + + //"Employee does not exist" + if (employees[_employeeAddress].employeeId == 0) { + revert EmployeeDoesNotExists(); + } + + //@todo can this be memory? + //Employee memory employee = employees[_employeeAddress]; + + uint256 weeklyPay = employees[_employeeAddress].isSalary + ? _calculateWeeklyPaymentSalary(_employeeAddress) + : _calculateWeeklyPaymentHourly(_employeeAddress, _hoursWorked); + + PaymentSplit memory splits = employees[_employeeAddress].paymentSplits; + + // Transfer tokens to each chain according to the payment split + if (splits.paySplitPercentageNative != 0) { + bool success = bnmToken.transfer( + _employeeAddress, + ((weeklyPay * splits.paySplitPercentageNative) / 100) + ); + require(success, "Token transfer failed"); + } + if (splits.paySplitPercentage1 != 0) { + _transferToChain( + i_destinationChainIdEth, + _employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage1) / 100) + ); + } + if (splits.paySplitPercentage2 != 0) { + _transferToChain( + i_destinationChainIDdOP, + _employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage2) / 100) + ); + } + if (splits.paySplitPercentage3 != 0) { + _transferToChain( + i_destinationChainIdAvax, + _employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage3) / 100) + ); + } + if (splits.paySplitPercentage4 != 0) { + _transferToChain( + i_destinationChainIdBnb, + _employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage4) / 100) + ); + } + //ccip.transferTokens(, _employeeAddress, , weeklyPay) + + emit SingleEmployeePaid(_employeeAddress, weeklyPay); + } + + function payAllSallaryEmployees() public onlyOwner { + for (uint i = 0; i < salariedEmployees.length; ++i) { + address employeeAddress = salariedEmployees[i]; + + uint256 weeklyPay = _calculateWeeklyPaymentSalary(employeeAddress); + + PaymentSplit memory splits = employees[employeeAddress] + .paymentSplits; + + // Transfer tokens to each chain according to the payment split + if (splits.paySplitPercentageNative != 0) { + bool success = bnmToken.transfer( + employeeAddress, + ((weeklyPay * splits.paySplitPercentageNative) / 100) + ); + require(success, "Token transfer failed"); + } + if (splits.paySplitPercentage1 != 0) { + _transferToChain( + i_destinationChainIdEth, + employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage1) / 100) + ); + } + if (splits.paySplitPercentage2 != 0) { + _transferToChain( + i_destinationChainIDdOP, + employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage2) / 100) + ); + } + if (splits.paySplitPercentage3 != 0) { + _transferToChain( + i_destinationChainIdAvax, + employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage3) / 100) + ); + } + if (splits.paySplitPercentage4 != 0) { + _transferToChain( + i_destinationChainIdBnb, + employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage4) / 100) + ); + } + } + + emit AllSallaryEmployeesPaid(); + } + + function payAllHourlyEmployees( + uint8[] calldata _hoursWorked + ) public onlyOwner { + if (hourlyEmployees.length != _hoursWorked.length) { + revert HoursWorkedArrayDoesNotMatchEmployees(_hoursWorked); + } + + for (uint i = 0; i < hourlyEmployees.length; ++i) { + address employeeAddress = hourlyEmployees[i]; + + uint256 weeklyPay = _calculateWeeklyPaymentHourly( + employeeAddress, + _hoursWorked[i] + ); + + PaymentSplit memory splits = employees[employeeAddress] + .paymentSplits; + + // Transfer tokens to each chain according to the payment split + if (splits.paySplitPercentageNative != 0) { + bool success = bnmToken.transfer( + employeeAddress, + ((weeklyPay * splits.paySplitPercentageNative) / 100) + ); + require(success, "Token transfer failed"); + } + if (splits.paySplitPercentage1 != 0) { + _transferToChain( + i_destinationChainIdEth, + employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage1) / 100) + ); + } + if (splits.paySplitPercentage2 != 0) { + _transferToChain( + i_destinationChainIDdOP, + employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage2) / 100) + ); + } + if (splits.paySplitPercentage3 != 0) { + _transferToChain( + i_destinationChainIdAvax, + employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage3) / 100) + ); + } + if (splits.paySplitPercentage4 != 0) { + _transferToChain( + i_destinationChainIdBnb, + employeeAddress, + address(bnmToken), + ((weeklyPay * splits.paySplitPercentage4) / 100) + ); + } + } + + emit AllHourlyEmployeesPaid(); + } + + function payAllEmployees(uint8[] calldata _hoursWorked) external onlyOwner { + payAllSallaryEmployees(); + payAllHourlyEmployees(_hoursWorked); + + emit AllEmployeesPaid(); + } + + function _transferToChain( + uint64 _destinationChainSelector, + address _receiver, + address _token, + uint256 _amount + ) internal returns (bytes32 messageId) { + bnmToken.approve(address(ccip), _amount); + + bool success = bnmToken.transfer(address(ccip), _amount); + require(success, "Token transfer failed"); + + messageId = ccip.transferTokensPayNative( + _destinationChainSelector, + _receiver, + _token, + _amount + ); + emit TransferToChain(_receiver, _destinationChainSelector, messageId); + } + + /////////////////////////////////////////////// + /// Only Owner Employee Managment Functions /// + /////////////////////////////////////////////// + + ///@dev if employee _isSalary = true then _payRate is their annual salary. + ///@dev if employee _isSalary = false then _payRate is their hourly rate. + function addEmployee( + address _employeeAddress, + bool _isSalary, + uint256 _payRate + ) external onlyOwner { + //"Invalid address" + if (_employeeAddress == address(0)) { + revert InvalidAddress(); + } + //"Employee already exists" + if (employees[_employeeAddress].employeeId != 0) { + revert EmployeeAlreadyExists(); + } + + if (_employeeAddress == owner()) { + revert OwnerCantBeEmployee(); + } + + if (_payRate == 0) { + revert MustHavePayRate(); + } + + // Generate a unique ID for the new employee + // Employee address has been created by Employer w/ account abstraction + bytes32 employeeId = keccak256( + abi.encodePacked(_employeeAddress, block.timestamp) + ); + + Employee memory newEmployee = Employee({ + employeeId: employeeId, + primaryWallet: _employeeAddress, + isSalary: _isSalary, + payRate: _payRate, + paymentSplits: PaymentSplit({ + paySplitPercentageNative: 100, //mumbai + paySplitPercentage1: 0, // eth + paySplitPercentage2: 0, // op + paySplitPercentage3: 0, // avax + paySplitPercentage4: 0 // bnb + }) + }); + + // update employees address mapping + employees[_employeeAddress] = newEmployee; + // update employeeId mapping + employeeIds[employeeId] = newEmployee; + + // update salary/hourly array and index mapping + if (_isSalary == true) { + salariedEmployees.push(_employeeAddress); + uint256 index = salariedEmployees.length - 1; + salariedEmployeeIndex[_employeeAddress] = index; + } else { + hourlyEmployees.push(_employeeAddress); + uint256 index = hourlyEmployees.length - 1; + hourlyEmployeeIndex[_employeeAddress] = index; + } + + emit EmployeeAdded(_employeeAddress); + } + + ///@dev if employee _isSalary = true then _payRate is their annual salary. + ///@dev if employee _isSalary = false then _payRate is their hourly rate. + function setEmployeeSalary( + address _employeeAddress, + bool _isSalary, + uint256 _payRate + ) external onlyOwner { + //"Invalid address" + if (_employeeAddress == address(0)) { + revert InvalidAddress(); + } + //"Employee does not exist" + if (employees[_employeeAddress].employeeId == 0) { + revert EmployeeDoesNotExists(); + } + + employees[_employeeAddress].isSalary = _isSalary; + employees[_employeeAddress].payRate = _payRate; + + emit SalarySet(_employeeAddress); + } + + function removeEmployee(address _employeeAddress) external onlyOwner { + // might not be necessary + if (employees[_employeeAddress].employeeId == 0) { + revert EmployeeDoesNotExists(); + } + + Employee storage employee = employees[_employeeAddress]; + + if (employee.isSalary == true) { + uint256 index = salariedEmployeeIndex[_employeeAddress]; + _removeFromArray(index, salariedEmployees); + } else { + uint256 index = hourlyEmployeeIndex[_employeeAddress]; + _removeFromArray(index, hourlyEmployees); + } + + //@todo do I need to update the mapping or address to employee struct too? see above + delete employees[_employeeAddress]; + + emit EmployeeFired(_employeeAddress); + } + + ////////////////////////////////////////////////////////// + /// Employee & Owner Only Emloyee Management Functions /// + ////////////////////////////////////////////////////////// + + function changePrimaryWalletAddress( + address _oldAddress, + address _newAddress + ) public onlyEmployee { + //"Invalid address" + if (_newAddress == address(0)) { + revert InvalidAddress(); + } + //"Employee does not exist" -- @todo don't think I need check because of onlyEmployee modifier + if (employees[_oldAddress].employeeId == 0) { + revert EmployeeDoesNotExists(); + } + + // Copy to memory the employee + Employee memory updatedEmployee = employees[_oldAddress]; + + //@todo might need to update the mapping as well or use the employeeId for the mapping key + updatedEmployee.primaryWallet = _newAddress; + + if (employees[_oldAddress].isSalary == true) { + // remove old wallet from appropriate array + uint256 oldIndex = salariedEmployeeIndex[_oldAddress]; + _removeFromArray(oldIndex, salariedEmployees); + + // add new wallet to appropriate array & update index mapping + salariedEmployees.push(_newAddress); + uint256 newIndex = salariedEmployees.length - 1; + salariedEmployeeIndex[_newAddress] = newIndex; + } else { + // remove old wallet from appropriate array + uint256 oldIndex = hourlyEmployeeIndex[_oldAddress]; + _removeFromArray(oldIndex, hourlyEmployees); + + // add new wallet to appropriate array & update index mapping + hourlyEmployees.push(_newAddress); + uint256 newIndex = hourlyEmployees.length - 1; + hourlyEmployeeIndex[_newAddress] = newIndex; + } + + //delete old address mapping -- do I need to delete and re-add? + delete employees[_oldAddress]; + + //update employee address mapping + employees[_newAddress] = updatedEmployee; + + emit WalletAddressChanged(_oldAddress, _newAddress); + } + + //@todo does _paySplit need to be callData? + function setPaymentSplits( + address _employeeAddress, + uint8[5] calldata _paySplitPercentages + ) public onlyEmployeeOrOwner { + uint8 totalPercentage = 0; + + for (uint i = 0; i < _paySplitPercentages.length; ++i) { + totalPercentage += _paySplitPercentages[i]; + } + + if (totalPercentage != 100) { + revert PercentageMustEqual100(); + } + + PaymentSplit memory newSplit = PaymentSplit({ + //@todo get the chainIds out of this struct + paySplitPercentageNative: _paySplitPercentages[0], // mumbai + paySplitPercentage1: _paySplitPercentages[1], // eth + paySplitPercentage2: _paySplitPercentages[2], // op + paySplitPercentage3: _paySplitPercentages[3], // avax + paySplitPercentage4: _paySplitPercentages[4] // bnb + }); + + Employee storage employee = employees[_employeeAddress]; + employee.paymentSplits = newSplit; + + emit PaymentSplitSet(_employeeAddress, _paySplitPercentages); + } + + // function setAutomationAddress(address _automationAddress) external onlyOwner { + // automationAddress = _automationAddress; + // } + //////////////////// + /// Helper Funcs /// + //////////////////// + + function _removeFromArray( + uint256 _index, + address[] storage array + ) internal { + if (_index >= array.length) return; + + for (uint i = _index; i < array.length - 1; ++i) { + array[i] = array[i + 1]; + } + + array.pop(); + } + + function _calculateWeeklyPaymentSalary( + address _employeeAddress + ) internal view returns (uint256 weeklyPay) { + weeklyPay = employees[_employeeAddress].payRate / 52; + } + + function _calculateWeeklyPaymentHourly( + address _employeeAddress, + uint8 _hoursWorked + ) internal view returns (uint256 weeklyPay) { + weeklyPay = employees[_employeeAddress].payRate * _hoursWorked; + } + + /////////////////////////////////// + /// VIEW & GETTER FUNCS + ////////////////////////////////// + function viewPaymentSplit( + address _employeeAddress + ) public view returns (PaymentSplit memory) { + return employees[_employeeAddress].paymentSplits; + } + + function getEmployee( + address _employeeAddress + ) public view returns (Employee memory) { + return employees[_employeeAddress]; + } + + function doesEmployeeExist( + address _employeeAddress + ) public view returns (bool) { + if (employees[_employeeAddress].employeeId == 0) { + return false; + } else { + return true; + } + } + + function isOwner(address _address) public view returns (bool) { + address owner = owner(); + if (_address == owner) { + return true; + } else { + return false; + } + } + + function getEmployeePaymentSplits( + address _employeeAddress + ) public view returns (PaymentSplit memory) { + return employees[_employeeAddress].paymentSplits; + } + + function getHourlyEmployees() public view returns (address[] memory) { + return hourlyEmployees; + } + + function getSalariedEmployees() public view returns (address[] memory) { + return salariedEmployees; + } + + // function getAutomationAddress() public view returns (address){ + // return automationAddress; + // } } diff --git a/packages/nextjs/components/dash-wind/features/employees/index.tsx b/packages/nextjs/components/dash-wind/features/employees/index.tsx index c517ac2..52858e9 100644 --- a/packages/nextjs/components/dash-wind/features/employees/index.tsx +++ b/packages/nextjs/components/dash-wind/features/employees/index.tsx @@ -8,9 +8,10 @@ import { CONFIRMATION_MODAL_CLOSE_TYPES, MODAL_BODY_TYPES } from "../../utils/gl import { openModal } from "../common/modalSlice"; /*-------------------------------------*/ -// Kaz & Trevor -// uncomment -//import { useContractRead } from "wagmi"; +// // Kaz & Trevor +// // uncomment +// import { useContractRead } from "wagmi"; +// import Payroll from "../../../../../hardhat/artifacts/contracts/Payroll.sol/Payroll.json"; /*-------------------------------------*/ import TrashIcon from "@heroicons/react/24/outline/TrashIcon"; @@ -39,10 +40,12 @@ const TopSideButtons = () => { /*-------------------------------------*/ // Kaz & Trevor -// uncomment +// uncomment @todo // const chainId = process.env.NEXT_PUBLIC_TARGET_LOCAL_CHAIN // ? process.env.NEXT_PUBLIC_LOCAL_CHAIN_ID // : process.env.NEXT_PUBLIC_TESTNET_CHAIN_ID; + +// const payrollABI = Payroll.abi; /*-------------------------------------*/ function Employees() { @@ -68,14 +71,14 @@ function Employees() { * This will allow for access to employee data in other components: EmployeeProfile, */ - // uncomment below + // // uncomment below @todo // const { // data: salariedEmployeeAddresses, // address[] // isError, // isLoading, // } = useContractRead({ // address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS, - // abi: payrollContractAbi, + // abi: payrollABI, // functionName: "getSalariedEmployees", // chainId: Number(chainId), // }); @@ -86,11 +89,51 @@ function Employees() { // isLoading, // } = useContractRead({ // address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS, - // abi: payrollContractAbi, + // abi: payrollABI, // functionName: "getHourlyEmployees", // chainId: Number(chainId), // }); + // // this will return a bool from contract as to if the address is an employee true = employee exists + // const { + // data: isEmployee, + // // isError, + // // isLoading, + // } = useContractRead({ + // address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS, + // abi: payrollABI, + // functionName: "isEmployee", + // //@todo where do we need to get this address from? + // // figured we should check if an address is an employee + // // before owner/company is allwoed to add or delete + // args: [userAddress], + // chainId: Number(chainId), + // }); + + // // hook to add employee to the contract @todo + // // need their address, a bool _isSalary, uint256 _payRate(hourly rate if isSalary is false, otherwise annual salary) + // const { data, isLoading, isSuccess, write } = useContractWrite({ + // address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS, + // abi: payrollABI, + // functionName: "addEmployee", + // args: [], + // onSuccess(data: any) { + // console.log("employee added! Data: ", data); //will data be the contract addresses? + // }, + // onError(error: any) { + // console.error("employee add error!", error); //error message + // }, + // }); + + // // hook to set or change an employees salary if we need it, not necessary for stuff rn + // // from Payroll: function setEmployeeSalary(address _employeeAddress, bool _isSalary, uint256 _payRate) external onlyOwner {} + // const { data, isLoading, isSuccess, write } = useContractWrite({ + // address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS, + // abi: payrollABI, + // functionName: "setEmployeeSalary", + // args: [employeeAddress, isSalary, payRate] + // }); + // match each employee address with corresponding employee in dummy data // function getEmployeesFromDB() { // // ... @@ -111,8 +154,15 @@ function Employees() { const deleteCurrentLead = (e: MouseEvent, index: number) => { /*-------------------------------------*/ - // Kaz & Trevor + // Kaz & Trevor @todo // this function will be responsible for deleting an employee + // WAGMI HOOK + // const { data, isLoading, isSuccess, write } = useContractWrite({ + // address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS, + // abi: payrollABI, + // functionName: "removeEmployee", + // args: [employeeAddress] + // }); /*-------------------------------------*/ e.stopPropagation(); diff --git a/packages/nextjs/components/dash-wind/features/payments/index.tsx b/packages/nextjs/components/dash-wind/features/payments/index.tsx index 9a60353..e1d8c6b 100644 --- a/packages/nextjs/components/dash-wind/features/payments/index.tsx +++ b/packages/nextjs/components/dash-wind/features/payments/index.tsx @@ -10,6 +10,57 @@ import { RECENT_PAYMENTS } from "../../utils/dummyData"; import FunnelIcon from "@heroicons/react/24/outline/FunnelIcon"; import XMarkIcon from "@heroicons/react/24/outline/XMarkIcon"; +// uncomment @todo +//import { useContractWrite } from "wagmi"; +//import Payroll from "../../../../../hardhat/artifacts/contracts/Payroll.sol/Payroll.json"; + +//@todo add buttons to pay single employee, pay all salary employees, pay all hourly employees, and pay all employees? + +// const chainId = process.env.NEXT_PUBLIC_TARGET_LOCAL_CHAIN +// ? process.env.NEXT_PUBLIC_LOCAL_CHAIN_ID +// : process.env.NEXT_PUBLIC_TESTNET_CHAIN_ID; + +// const payrollABI = Payroll.abi; +// const payrollAddress = process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS; + +//WAGMI HOOKS @todo + +// paySingleEmployee args: address _employeeAddress, uint8 hoursWorked - will be 0 if salary +// const { data, isLoading, isSuccess, write } = useContractWrite({ +// address: payrollAddress, +// abi: payrollABI, +// functionName: "paySingleEmployee", +// args: [employeeAddress, hoursWorked], +// }); + +// Pay All Salary +// from Payroll: function payAllSallaryEmployees() public onlyOwner {} +// const { data, isLoading, isSuccess, write } = useContractWrite({ +// address: payrollAddress, +// abi: payrollABI, +// functionName: "payAllSallaryEmployees", + +// Pay All Hourly +// from Payroll: function payAllHourlyEmployees(uint8[] calldata _hoursWorked) public onlyOwner {} +// the array of hours worked needs to be the same length as the number of hourly employees +// And it must match up with the order of the employees in the hourlyEmployees array so the correct employee gets the correct pament +// const { data, isLoading, isSuccess, write } = useContractWrite({ +// address: payrollAddress, +// abi: payrollABI, +// functionName: "payAllHourlyEmployees", +// args: [hoursWorked], // this needs to be an array of uint8 +// }); + +// Pay All Employees +// from Payroll: function payAllEmployees(uint8[] calldata _hoursWorked) external onlyOwner {} +// this function calls payAllSallaryEmployees first and payAllHourlyEmployees. The array of hours worked needs to be the same length as the number of hourly employees and match up with the correct employee +// const { data, isLoading, isSuccess, write } = useContractWrite({ +// address: payrollAddress, +// abi: payrollABI, +// functionName: "payAllEmployees", +// args: [hoursWorked], // this needs to be an array of uint8 +// }); + interface TopSideButtonsProps { applySearch: (value: string) => void; applyFilter: (params: string) => void; diff --git a/packages/nextjs/components/dash-wind/features/settings/profilesettings/index.tsx b/packages/nextjs/components/dash-wind/features/settings/profilesettings/index.tsx index 42b8d0c..de1a503 100644 --- a/packages/nextjs/components/dash-wind/features/settings/profilesettings/index.tsx +++ b/packages/nextjs/components/dash-wind/features/settings/profilesettings/index.tsx @@ -1,10 +1,13 @@ // import { useEffect, useState } from "react"; +//import Payroll from "../../../../../hardhat/artifacts/contracts/Payroll.sol/Payroll.json"; import TitleCard from "../../../components/Cards/TitleCard"; import InputText from "../../../components/Input/InputText"; import TextAreaInput from "../../../components/Input/TextAreaInput"; import ToggleInput from "../../../components/Input/ToggleInput"; import { UpdateFormValues } from "../../../types/FormTypes"; import { showNotification } from "../../common/headerSlice"; +// uncomment +//import { useContractWrite } from "wagmi"; // import moment from "moment"; import { useMyDispatch } from "~~/components/dash-wind/app/store"; @@ -16,6 +19,25 @@ function ProfileSettings() { dispatch(showNotification({ message: "Profile Updated", status: 1 })); }; + //@todo add button for employee to update wallet address + // from payroll: function changePrimaryWalletAddress(address _oldAddress, address _newAddress) public onlyEmployee {} + // const { data, isLoading, isSuccess, write } = useContractWrite({ + // address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS, + // abi: payrollABI, + // functionName: "changePrimaryWalletAddress", + // args: [oldAddress, newAddress], + // }); + + //@todo add button/form/someway for employee to update their payment splits, which chains how much. Might want to be in Employee and Company dashboards? + // from payroll: function setPaymentSplits(address _employeeAddress, uint8[5] calldata _paySplitPercentages) public onlyEmployeeOrOwner {} + // payment splits [0] = native chain, [1] = ethereum, [2] = optismium, [3] = avlanche, [4] = binance + // const { data, isLoading, isSuccess, write } = useContractWrite({ + // address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS, + // abi: payrollABI, + // functionName: "setPaymentSplits", + // args: [employeeAddress, paySplitPercentages/**This needs to be an array with length of 5 */], + // }); + const updateFormValue = ({ updateType }: UpdateFormValues) => { console.log(updateType); }; diff --git a/packages/nextjs/components/dash-wind/features/user/Login.tsx b/packages/nextjs/components/dash-wind/features/user/Login.tsx index 156908f..c59a80e 100644 --- a/packages/nextjs/components/dash-wind/features/user/Login.tsx +++ b/packages/nextjs/components/dash-wind/features/user/Login.tsx @@ -6,25 +6,19 @@ import InputText from "../../components/Input/InputText"; import ErrorText from "../../components/Typography/ErrorText"; import { UpdateFormValues } from "../../types/FormTypes"; import LandingIntro from "./LandingIntro"; -import { Address, createWalletClient, custom } from "viem"; +import { Address, createWalletClient, custom, isAddress } from "viem"; import { polygonMumbai } from "viem/chains"; // import { Address, createWalletClient, custom } from "viem"; // import { polygonMumbai } from "viem/chains"; -import { useAccount, useConnect, useContractRead } from "wagmi"; -import { - setIsAdmin, - /*setIsAdmin,*/ - setIsConnected, -} from "~~/auth/authSlice"; +import { useConnect, useContractRead } from "wagmi"; +import { setIsAdmin, setIsConnected } from "~~/auth/authSlice"; import { web3auth } from "~~/auth/web3auth"; import { useMyDispatch } from "~~/components/dash-wind/app/store"; - -//import { get } from "http"; +import { useDebounce } from "~~/components/web-3-crew/hooks/useDebounce"; function Login() { const INITIAL_LOGIN_OBJ = { contractAddress: "", - emailId: "", }; const chainId = //process.env.NEXT_PUBLIC_TARGET_LOCAL_CHAIN ? @@ -32,87 +26,158 @@ function Login() { //: process.env.NEXT_PUBLIC_TESTNET_CHAIN_ID; - // const [loading, setLoading] = useState(false); + const payrollABI = Payroll.abi; + const [errorMessage, setErrorMessage] = useState(""); const [loginObj, setLoginObj] = useState(INITIAL_LOGIN_OBJ); - // const { isConnected } = useMySelector((state: MyState) => state.auth); + const debouncedLoginObj = useDebounce<{ contractAddress: string }>(loginObj, 1000); const router = useRouter(); const dispatch = useMyDispatch(); - //const userAddress = getAccounts(); // state to store user address once it is fetched const [userAddress, setUserAddress] = useState(null); - const { address: wagmiAddress, isConnected: wagmiIsConnected } = useAccount(); const { connect, connectors, error } = useConnect(); + const updateFormValue = ({ updateType, value }: UpdateFormValues) => { + setErrorMessage(""); + setLoginObj({ ...loginObj, [updateType]: value.trim() }); + }; + useEffect(() => { + // Get user address on render if the account is already logged in async function fetchAddress() { const address = await getAccounts(); if (address) { - // not sure what this issue is with this setUserAddress(address); } } fetchAddress(); }, []); - const payrollABI = Payroll.abi; - - /*-------------------------------------*/ - // Kaz & Trevor // getOwner address to test against user's address - // need to see what shape `owner` will be on return - // contract interaction calls isOwner() const { data: isOwner, - // isError, - // isLoading, + isLoading: isOwnerLoading, + isSuccess: isOwnerSuccess, + isError: isOwnerError, + }: { + data: boolean | undefined; + isLoading: boolean | undefined; + isSuccess: boolean | undefined; + isError: boolean | undefined; } = useContractRead({ - address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS, + address: debouncedLoginObj.contractAddress ? debouncedLoginObj.contractAddress : "", abi: payrollABI, functionName: "isOwner", args: userAddress ? [userAddress] : [], chainId: Number(chainId), - }) as { data: boolean | undefined }; + onSuccess(data) { + console.log("useContractRead - isOwner: ", data); + }, + onError(err) { + console.error("useContractRead - isOwner: ", err); + }, + }); + + // // this will return a bool from contract as to if the address is an employee true = employee exists + const { + data: isEmployee, + isLoading: isEmployeeLoading, + isSuccess: isEmployeeSuccess, + isError: isEmployeeError, + }: { + data: boolean | undefined; + isLoading: boolean | undefined; + isSuccess: boolean | undefined; + isError: boolean | undefined; + } = useContractRead({ + address: debouncedLoginObj.contractAddress ? debouncedLoginObj.contractAddress : "", + abi: payrollABI, + functionName: "doesEmployeeExist", + args: userAddress ? [userAddress] : [], + chainId: Number(chainId), + onSuccess(data) { + console.log("useContractRead - isEmployee: ", data); + }, + onError(err) { + console.error("useContractRead - isEmployee: ", err); + }, + }); - console.log("is owner: ", isOwner); - console.log("wagmi account address: ", wagmiAddress); - console.log("wagmi account isConnected: ", wagmiIsConnected); + console.log("isOwnerSuccess: ", isOwnerSuccess); + console.log("isEmployeeSuccess: ", isEmployeeSuccess); - // Wagmi Connect - async function login() { - if (web3auth.connected) { - dispatch(setIsConnected({ isConnected: true })); - if (!isOwner) { - //until the hook is working, this is going to prevent us from being directed to the dashboard - dispatch(setIsAdmin({ isAdmin: false })); - router.push("/dapp/dashboard"); - return; - } + useEffect(() => { + console.log("is owner: ", isOwner); + console.log("is employee: ", isEmployee); + // console.log("login 8.5"); + if (isOwner) { + // console.log("login 9"); dispatch(setIsAdmin({ isAdmin: true })); router.push("/dapp/dashboard"); return; } + if (isEmployee) { + // console.log("login 10"); + dispatch(setIsAdmin({ isAdmin: false })); + router.push("/dapp/dashboard"); + return; + } + // not owner or employee + if (isOwner === false && isEmployee === false) { + dispatch(setIsAdmin({ isAdmin: false })); + setErrorMessage( + `Sorry, your account is not currently connected to this contract. + Double check the contract address and use the same login method you used when registering your account.`, + ); + } + }, [isOwnerSuccess, isEmployeeSuccess]); + useEffect(() => { + if (loginObj.contractAddress !== "" && !isAddress(loginObj.contractAddress)) { + setErrorMessage("Value entered is not valid address"); + return; + } + // update form error msg is hook comes back with error + if (isEmployeeSuccess && isEmployeeError) { + setErrorMessage("Error while checking employee status"); + } + if (isOwnerSuccess && isOwnerError) { + setErrorMessage("Error while checking owner status"); + } + }, [isEmployeeError, isEmployeeSuccess, isOwnerError, isOwnerSuccess]); + + // Wagmi Connect + async function login() { + // console.log("login 1"); try { + // console.log("login 6"); await web3auth.connect(); connect({ connector: connectors[6] }); if (error) { console.error("wagmi connect error: from Login - login(): ", error); } if (web3auth.connected) { + // console.log("login 7"); dispatch(setIsConnected({ isConnected: true })); + const address = await getAccounts(); // Retrieve user's address if (address) { + // console.log("login 8"); setUserAddress(address); - } - if (!isOwner) { - // until the hook is working, this is going to prevent us from being directed to the dashboard - dispatch(setIsAdmin({ isAdmin: false })); - router.push("/dapp/dashboard"); return; } - dispatch(setIsAdmin({ isAdmin: true })); - router.push("/dapp/dashboard"); + // ERROR MSG + setErrorMessage("No account address found"); + /////////////////////////// + // Previous working logic - without `isEmployee` check + // if (!isOwner) { + // // until the hook is working, this is going to prevent us from being directed to the dashboard + // dispatch(setIsAdmin({ isAdmin: false })); + // router.push("/dapp/dashboard"); + // return; + // } + // dispatch(setIsAdmin({ isAdmin: true })); + // router.push("/dapp/dashboard"); } } catch (error) { console.error(error); @@ -131,7 +196,6 @@ function Login() { transport: custom(web3auth.provider), }); - //console.log("web3auth provider: ", web3auth.provider); // Get user's public address const [userAddress] = await client.getAddresses(); console.log("user address: ", userAddress); @@ -142,15 +206,9 @@ function Login() { e.preventDefault(); setErrorMessage(""); - // if (loginObj.emailId.trim() === "") return setErrorMessage("Email is required!"); if (loginObj.contractAddress.trim() === "") return setErrorMessage("Contract Address is required!"); }; - const updateFormValue = ({ updateType, value }: UpdateFormValues) => { - setErrorMessage(""); - setLoginObj({ ...loginObj, [updateType]: value }); - }; - return (
@@ -169,22 +227,28 @@ function Login() { labelTitle="Contract Address" updateFormValue={updateFormValue} /> - - {/* */}
+

isOwner: {isOwner}

+

isEmployee: {isEmployee}

- {errorMessage} - + {errorMessage} +
Don't have an account yet?{" "} diff --git a/packages/nextjs/components/web-3-crew/hooks/useDebounce.ts b/packages/nextjs/components/web-3-crew/hooks/useDebounce.ts new file mode 100644 index 0000000..2b32a74 --- /dev/null +++ b/packages/nextjs/components/web-3-crew/hooks/useDebounce.ts @@ -0,0 +1,15 @@ +import { useEffect, useState } from "react"; + +export function useDebounce(value: T, delay?: number): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebouncedValue(value), delay || 500); + + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + + return debouncedValue; +} diff --git a/packages/nextjs/components/web-3-crew/register-page/form.tsx b/packages/nextjs/components/web-3-crew/register-page/form.tsx deleted file mode 100644 index 8bb37de..0000000 --- a/packages/nextjs/components/web-3-crew/register-page/form.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import Link from "next/link"; -import PayrollFactory from "../../../../hardhat/artifacts/contracts/PayrollFactory.sol/PayrollFactory.json"; -import { Address, parseEther } from "viem"; -import { useContractWrite, usePrepareContractWrite } from "wagmi"; -import ErrorText from "~~/components/dash-wind/components/Typography/ErrorText"; -import { Address as AddressDisplay } from "~~/components/scaffold-eth/Address"; - -interface props { - ownerAddress: Address | null; -} - -const payrollFactoryAddress = ""; -const payrollFactoryABI = PayrollFactory.abi; - -export default function DeployForm({ ownerAddress }: props) { - const { config } = usePrepareContractWrite({ - address: payrollFactoryAddress, - abi: payrollFactoryABI, - functionName: "deployPayrollAndTokenTransferor", - value: parseEther("1", "wei"), - onSuccess(data) { - console.log("contract deployed! Data: ", data); - }, - onError(error) { - console.error("contract deploy error!", error); - }, - }); - - const { data, isLoading, isSuccess, write } = useContractWrite(config); - - return ( -
-

Deploy Payroll Contract

-
- {ownerAddress ? ( - <> -
-

Account Owner Address:

- -
- {/* - - ) : ( - Error: Owner Address Not Found - )} - - {data && ( -
-

Payroll Contract Address:

- {/*

{data}

*/} - {/* */} -
- )} - -
- - - Go Home - - -
-
-
- ); -} diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index a91e4ec..19e98bc 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -2,7 +2,7 @@ import { ReactElement, useEffect } from "react"; // import Link from "next/link"; import { useRouter } from "next/router"; import type { NextPageWithLayout } from "./_app"; -import { setIsConnected } from "~~/auth/authSlice"; +// import { setIsConnected } from "~~/auth/authSlice"; import { web3auth } from "~~/auth/web3auth"; import { MetaHeader } from "~~/components/MetaHeader"; import { MyState, useMyDispatch, useMySelector } from "~~/components/dash-wind/app/store"; @@ -15,15 +15,17 @@ const LandingPage: NextPageWithLayout = () => { useEffect(() => { if (web3auth.connected) { - dispatch(setIsConnected({ isConnected: true })); + web3auth.logout(); + // dispatch(setIsConnected({ isConnected: true })); } }, [dispatch]); function launchDapp() { if (isConnected) { // redirect to dashboard if logged in - router.push("/dapp/dashboard"); - return; + // web3auth.logout(); + // router.push("/dapp/dashboard"); + // return; } // redirect to login if not logged in router.push("/login");