Skip to content

Commit

Permalink
feat(solidr-front-next): migrate members tab components
Browse files Browse the repository at this point in the history
  • Loading branch information
Teddy CHAMBARD committed Aug 20, 2024
1 parent fd01361 commit d068e7a
Show file tree
Hide file tree
Showing 17 changed files with 917 additions and 77 deletions.
66 changes: 66 additions & 0 deletions packages/fronts/solidr-front-next/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
}
}
},
"join": {
"title": "Join session {name}",
"form": {
"memberName": {
"label": "Your member name",
"required": "Member name is required"
}
}
},
"action": {
"close": {
"menu": {
Expand Down Expand Up @@ -83,6 +92,62 @@
}
}
},
"summary": {
"totalCost": "My total cost",
"totalExpenses": "Total expenses",
"totalRefunds": "Total refunds"
},
"share": {
"title": "Share",
"generatedUrl": {
"title": "Generated url"
},
"generateLink": {
"title": "Generate new link"
}
},
"members": {
"list": {
"title": "List of members"
},
"add": {
"title": "Add a new member",
"form": {
"address": {
"label": "Address",
"required": "Member address is required"
},
"memberName": {
"label": "Member name",
"required": "Member name is required"
}
}
},
"edit": {
"title": "Edit member",
"form": {
"memberName": {
"label": "Member name",
"required": "Member name is required"
}
}
}
},
"expenses": {
"add": {
"title": "Add a new expense",
"form": {
"name": {
"label": "Name",
"required": "Expense name is required"
},
"amount": {
"label": "Amount",
"required": "Expense amount is required"
}
}
}
},
"tabs": {
"members": "Members",
"expenses": "Operations",
Expand All @@ -92,6 +157,7 @@
"common": {
"submit": "Submit",
"cancel": "Cancel",
"confirm": "Confirm",
"goToHome": "Go to home"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { Suspense, useState } from 'react';
import { useForm } from 'react-hook-form-mui';
import { DialogTitle } from '@headlessui/react';
import { useRecoilValue } from 'recoil';
import { useAnchorWallet } from '@solana/wallet-adapter-react';
import { Wallet } from '@coral-xyz/anchor';
import { sessionCurrentState } from '@/store/sessions';
import { PublicKey } from '@solana/web3.js';
import { useTranslations } from 'next-intl';
import Dialog from '@/components/Dialog';
import { useSolidrClient } from '@/providers/solidr/SolidrClientContext';

interface IAddMemberDialogProps {
dialogVisible: boolean;
setDialogVisible: React.Dispatch<React.SetStateAction<boolean>>;
}

interface IRegisterMemberParams {
address: string;
memberName: string;
}

export default ({ dialogVisible, setDialogVisible }: IAddMemberDialogProps) => {

const t = useTranslations();

const anchorWallet = useAnchorWallet() as Wallet;
const solidrClient = useSolidrClient();
const { session } = useRecoilValue(sessionCurrentState);

const [formData, setFormData] = useState<Partial<IRegisterMemberParams>>({});

const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({
defaultValues: formData,
});

const onSubmit = async (data: Partial<IRegisterMemberParams>) => {
if (!solidrClient || !session || !data.address || !data.memberName) return;
setFormData(data);
await solidrClient.addSessionMember(anchorWallet, session.sessionId, new PublicKey(data.address), data.memberName);
setDialogVisible(false);
};

if (!anchorWallet || !solidrClient || !session) return <Suspense />;

return (
<Dialog isOpen={dialogVisible} onClose={() => setDialogVisible(false)}>
<div className="sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:text-center">
<DialogTitle as="h3" className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
{t('session.members.add.title')}
</DialogTitle>
</div>
<hr className="my-4 border-gray-300 dark:border-gray-700" />
</div>
<div className="mt-5 sm:mt-4">
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 sm:space-y-6">
<div>
<label className="block text-gray-900 dark:text-white mb-1" htmlFor="address">
{t('session.members.add.form.address.label')}
</label>
<input
type="text"
id="address"
{...register('address', { required: true })}
className="w-full px-4 py-2 text-gray-900 dark:text-white bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-customBlue focus:outline-none"
/>
{errors.address && <span className="text-red-500 text-sm">{t('session.members.add.form.address.required')}</span>}
</div>

<div>
<label className="block text-gray-900 dark:text-white mb-1" htmlFor="memberName">
{t('session.members.add.form.memberName.label')}
</label>
<input
type="text"
id="memberName"
{...register('memberName', { required: true })}
className="w-full px-4 py-2 text-gray-900 dark:text-white bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-customBlue focus:outline-none"
/>
{errors.memberName && <span className="text-red-500 text-sm">{t('session.member.add.form.memberName.required')}</span>}
</div>

<div className="flex space-x-4 pt-4">
<button
type="submit"
disabled={isSubmitting}
className="w-full px-4 py-2 bg-customBlue hover:bg-customBlueLight text-white font-semibold rounded-lg focus:ring-2 focus:ring-customBlue focus:outline-none"
>
{t('common.submit')} &gt;
</button>
<button
type="button"
className="px-4 py-2 text-customBlue hover:text-customBlueLight font-semibold focus:outline-none"
onClick={() => setDialogVisible(false)}
>
{t('common.cancel')}
</button>
</div>
</form>
</div>
</Dialog>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"use client";

import { useRecoilValue } from 'recoil';
import { sessionCurrentState } from '@/store/sessions';
import { Bar } from 'react-chartjs-2';
import { BarElement, CategoryScale, Chart as ChartJS, LinearScale, Tooltip as ChartTooltip, ChartOptions } from 'chart.js/auto';
import { hexToRgba, stringToColor } from '@/services/colors';
import { useTheme } from 'next-themes';

ChartJS.register(BarElement, CategoryScale, LinearScale, ChartTooltip);

export default () => {
const { theme } = useTheme();
const sessionCurrent = useRecoilValue(sessionCurrentState);

const sortedBalances = Object.values(sessionCurrent.balances).sort((a, b) => {
// Sort in descending order, with positive balances first, then negative balances
if (a.balance >= 0 && b.balance >= 0) {
return b.balance - a.balance;
} else if (a.balance < 0 && b.balance < 0) {
return a.balance - b.balance;
} else {
return b.balance >= 0 ? -1 : 1;
}
});

return <Bar
data={{
labels: sortedBalances.map((balance) => sessionCurrent.members[balance.owner.toString()].name),
datasets: [
{
data: sortedBalances.map((balance) => balance.balance),
backgroundColor: sortedBalances.map((balance) => hexToRgba(stringToColor(balance.owner.toString()), 1).toString()),
borderColor: sortedBalances.map((balance) => stringToColor(balance.owner.toString())),
hoverBackgroundColor: sortedBalances.map((balance) => hexToRgba(stringToColor(balance.owner.toString()), 0.5).toString()),
barPercentage: 0.9,
borderWidth: 1,
},
],
}}
options={{
plugins: {
title: {
display: false,
text: 'balance of costs',
position: 'bottom' as const,
},
legend: {
display: false,
},
customLabels: {
font: {
size: 12,
weight: 'bold',
},
},
} as any,
scales: {
x: {
display: false, // Hide x-axis completely
},
y: {
display: true, // Hide y-axis labels and ticks
beginAtZero: true,
grid: {
drawBorder: false,
color: (context: any) => {
if (context.tick.value === 0) {
return theme === 'light' ? 'rgba(0, 0, 0, 0.1)' : 'rgba(255, 255, 255, 0.1)'; // Color of the zero line
}
return 'rgba(0, 0, 0, 0)'; // Transparent color for other grid lines
},
} as any,
ticks: {
display: false,
},
border: {
display: false,
},
},
},
layout: {
padding: {
top: 40,
right: 40,
bottom: 40,
left: 40,
},
},
maintainAspectRatio: false, // Set this to false to allow the chart to resize freely
responsive: true, // Set this to true to make the chart responsive
}}
plugins={[{
id: 'customLabels',
afterDatasetsDraw(chart, args, pluginOptions) {
const { ctx, data, scales } = chart;

ctx.save();
ctx.font = `${pluginOptions?.font.weight} ${pluginOptions?.font.size}px sans-serif`;
ctx.textAlign = 'center';

data.datasets[0].data.forEach((value, index) => {
const x = scales.x.getPixelForValue(index);
const y = scales.y.getPixelForValue(Number(value));

ctx.fillStyle = Number(value) >= 0 ? '#1dc17d' : '#e36f6f';
const yPos = Number(value) >= 0 ? y - 10 : y + 20;
ctx.fillText(`${Number(value).toFixed(2)}$`, x, yPos);
});

ctx.restore();
},
}]}
/>;
};
Loading

0 comments on commit d068e7a

Please sign in to comment.