Skip to content

Commit 707c194

Browse files
et aussi, faut que les entreprise puisse récup leur bitcoin non réclamée
1 parent 465fe2a commit 707c194

File tree

2 files changed

+137
-66
lines changed

2 files changed

+137
-66
lines changed

src/app/companies/[companyId]/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { getRigById } from '@/lib/mining';
1515
import { ManageMembersDialog } from '@/components/manage-members-dialog';
1616
import { WithdrawCompanyCashDialog } from '@/components/withdraw-company-cash-dialog';
1717
import { ListCompanyButton } from '@/components/list-company-button';
18+
import { claimCompanyBtc } from '@/lib/actions/companies';
1819

1920

2021
function getInitials(name: string) {
@@ -47,6 +48,8 @@ export default async function CompanyDetailPage({ params }: { params: { companyI
4748
const rigData = getRigById(ownedRig.rigId);
4849
return total + (rigData?.hashRateMhs || 0) * ownedRig.quantity;
4950
}, 0);
51+
52+
const claimCompanyBtcWithId = claimCompanyBtc.bind(null, company.id);
5053

5154
return (
5255
<div className="space-y-6">
@@ -150,6 +153,11 @@ export default async function CompanyDetailPage({ params }: { params: { companyI
150153
<CardContent>
151154
<div className="text-2xl font-bold">{company.unclaimedBtc.toFixed(8)} BTC</div>
152155
<p className="text-xs text-muted-foreground">Généré par les opérations de minage</p>
156+
{isCEO && company.unclaimedBtc > 1e-9 && (
157+
<form action={claimCompanyBtcWithId}>
158+
<Button size="sm" className="mt-4 w-full" type="submit">Réclamer les BTC</Button>
159+
</form>
160+
)}
153161
</CardContent>
154162
</Card>
155163
</div>

src/lib/actions/companies.ts

Lines changed: 129 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ export async function createCompany(values: z.infer<typeof createCompanySchema>)
6262
role: 'ceo',
6363
});
6464

65-
// The creator is also the first shareholder
6665
await tx.insert(companyShares).values({
6766
companyId: newCompany.id,
6867
userId: session.id,
@@ -86,77 +85,71 @@ export async function createCompany(values: z.infer<typeof createCompanySchema>)
8685
}
8786

8887
export async function getCompaniesForUserDashboard() {
89-
const session = await getSession();
90-
91-
const allCompanies = await db.query.companies.findMany({
92-
orderBy: (companies, { desc }) => [desc(companies.createdAt)],
93-
});
94-
95-
const companiesWithMarketData = allCompanies.map(company => {
96-
const cash = parseFloat(company.cash);
97-
const totalShares = parseFloat(company.totalShares);
98-
const sharePrice = parseFloat(company.sharePrice);
99-
const marketCap = totalShares * sharePrice;
100-
101-
return {
102-
...company,
103-
cash: cash,
104-
marketCap: marketCap,
105-
sharePrice: sharePrice,
106-
totalShares: totalShares,
107-
}
108-
});
109-
110-
111-
if (!session?.id) {
112-
return {
113-
managedCompanies: [],
114-
investedCompanies: [],
115-
otherCompanies: companiesWithMarketData,
116-
};
117-
}
88+
const session = await getSession();
11889

119-
const [userMemberships, userShares] = await Promise.all([
120-
db.query.companyMembers.findMany({ where: eq(companyMembers.userId, session.id) }),
121-
db.query.companyShares.findMany({ where: eq(companyShares.userId, session.id) }),
122-
]);
90+
const allCompanies = await db.query.companies.findMany({
91+
orderBy: (companies, { desc }) => [desc(companies.createdAt)],
92+
});
12393

124-
const managedCompanyIds = new Set(userMemberships.map(m => m.companyId));
125-
const investedCompanyIds = new Set(userShares.map(s => s.companyId));
94+
const companiesWithMarketData = allCompanies.map(company => {
95+
const cash = parseFloat(company.cash);
96+
const totalShares = parseFloat(company.totalShares);
97+
const sharePrice = parseFloat(company.sharePrice);
98+
const marketCap = totalShares * sharePrice;
12699

127-
const managedCompanies: any[] = [];
128-
const investedCompanies: any[] = [];
129-
const otherCompanies: any[] = [];
100+
return {
101+
...company,
102+
cash: cash,
103+
marketCap: marketCap,
104+
sharePrice: sharePrice,
105+
totalShares: totalShares,
106+
}
107+
});
130108

131-
companiesWithMarketData.forEach(company => {
132-
const isManaged = managedCompanyIds.has(company.id);
133-
const isInvested = investedCompanyIds.has(company.id);
109+
if (!session?.id) {
110+
return {
111+
managedCompanies: [],
112+
investedCompanies: [],
113+
otherCompanies: companiesWithMarketData,
114+
};
115+
}
134116

135-
if (isManaged) {
136-
const membership = userMemberships.find(m => m.companyId === company.id)!;
137-
const shareData = userShares.find(s => s.companyId === company.id);
138-
const sharesHeld = parseFloat(shareData?.quantity || '0');
139-
140-
managedCompanies.push({
141-
...company,
142-
role: membership.role,
143-
sharesHeld: sharesHeld,
144-
sharesValue: sharesHeld * company.sharePrice,
145-
});
146-
} else if (isInvested) {
147-
const share = userShares.find(s => s.companyId === company.id)!;
148-
const sharesHeld = parseFloat(share.quantity);
149-
investedCompanies.push({
150-
...company,
151-
sharesHeld: sharesHeld,
152-
sharesValue: sharesHeld * company.sharePrice,
153-
});
154-
} else {
155-
otherCompanies.push(company);
117+
const [userMemberships, userShares] = await Promise.all([
118+
db.query.companyMembers.findMany({ where: eq(companyMembers.userId, session.id) }),
119+
db.query.companyShares.findMany({ where: eq(companyShares.userId, session.id) }),
120+
]);
121+
122+
const sharesByCompanyId = new Map(userShares.map(s => [s.companyId, s]));
123+
const membershipsByCompanyId = new Map(userMemberships.map(m => [m.companyId, m]));
124+
125+
const managedCompanies: any[] = [];
126+
const investedCompanies: any[] = [];
127+
const otherCompanies: any[] = [];
128+
129+
for (const company of companiesWithMarketData) {
130+
const membership = membershipsByCompanyId.get(company.id);
131+
const shareData = sharesByCompanyId.get(company.id);
132+
const sharesHeld = parseFloat(shareData?.quantity || '0');
133+
134+
if (membership) {
135+
managedCompanies.push({
136+
...company,
137+
role: membership.role,
138+
sharesHeld: sharesHeld,
139+
sharesValue: sharesHeld * company.sharePrice,
140+
});
141+
} else if (shareData) {
142+
investedCompanies.push({
143+
...company,
144+
sharesHeld: sharesHeld,
145+
sharesValue: sharesHeld * company.sharePrice,
146+
});
147+
} else {
148+
otherCompanies.push(company);
149+
}
156150
}
157-
});
158151

159-
return { managedCompanies, investedCompanies, otherCompanies };
152+
return { managedCompanies, investedCompanies, otherCompanies };
160153
}
161154

162155
export type ManagedCompany = Awaited<ReturnType<typeof getCompaniesForUserDashboard>>['managedCompanies'][0];
@@ -680,7 +673,7 @@ export async function applyMarketImpactToCompany(ticker: string, tradeValue: num
680673
revalidatePath('/');
681674
revalidatePath('/companies', 'layout');
682675

683-
} catch (error) {
676+
} catch (error: any) {
684677
console.error(`Error applying market impact to ${ticker}:`, error);
685678
}
686679
}
@@ -790,3 +783,73 @@ export async function sellAssetForCompany(companyId: number, holdingId: number,
790783
return { error: error.message || "Une erreur est survenue lors de la vente." };
791784
}
792785
}
786+
787+
export async function claimCompanyBtc(companyId: number): Promise<{ success?: string; error?: string }> {
788+
const session = await getSession();
789+
if (!session?.id) return { error: "Non autorisé." };
790+
791+
try {
792+
const result = await db.transaction(async (tx) => {
793+
const member = await tx.query.companyMembers.findFirst({
794+
where: and(eq(companyMembers.companyId, companyId), eq(companyMembers.userId, session.id))
795+
});
796+
if (!member || member.role !== 'ceo') {
797+
throw new Error("Seul le PDG peut réclamer les récompenses de minage.");
798+
}
799+
800+
const company = await tx.query.companies.findFirst({
801+
where: eq(companies.id, companyId),
802+
columns: { unclaimedBtc: true }
803+
});
804+
if (!company) throw new Error("Entreprise non trouvée.");
805+
806+
const amountBtc = parseFloat(company.unclaimedBtc);
807+
if (amountBtc < 1e-9) { // Avoid claiming dust
808+
throw new Error("Pas assez de BTC à réclamer.");
809+
}
810+
811+
const btcAsset = await tx.query.assets.findFirst({ where: eq(assetsSchema.ticker, 'BTC') });
812+
if (!btcAsset) throw new Error("L'actif BTC n'a pas été trouvé dans le système.");
813+
814+
const existingHolding = await tx.query.companyHoldings.findFirst({
815+
where: and(eq(companyHoldings.companyId, companyId), eq(companyHoldings.ticker, 'BTC'))
816+
});
817+
818+
if (existingHolding) {
819+
const newQuantity = parseFloat(existingHolding.quantity) + amountBtc;
820+
// We don't change the average cost as these are mined "for free" (in-game)
821+
await tx.update(companyHoldings)
822+
.set({ quantity: newQuantity.toString(), updatedAt: new Date() })
823+
.where(eq(companyHoldings.id, existingHolding.id));
824+
} else {
825+
await tx.insert(companyHoldings).values({
826+
companyId: companyId,
827+
ticker: 'BTC',
828+
name: 'Bitcoin',
829+
type: 'Crypto',
830+
quantity: amountBtc.toString(),
831+
avgCost: '0',
832+
});
833+
}
834+
835+
// Reset unclaimed BTC for the company
836+
await tx.update(companies)
837+
.set({ unclaimedBtc: '0', lastMiningUpdateAt: new Date() })
838+
.where(eq(companies.id, companyId));
839+
840+
return { success: `Vous avez réclamé ${amountBtc.toFixed(8)} BTC pour l'entreprise.` };
841+
});
842+
843+
revalidatePath(`/companies/${companyId}`);
844+
return result;
845+
846+
} catch (error: any) {
847+
console.error("Error claiming company BTC: ", error);
848+
// This is a server action called from a form, so we can't easily return the error to a toast.
849+
// It will fail and the user will see the old value. They can try again.
850+
// For a better UX, we would need a client component with state management.
851+
// But for now, this is a safe failure mode.
852+
// To show an error, we would have to redirect with a query param or similar.
853+
return { error: error.message || "Une erreur est survenue lors de la réclamation des récompenses." };
854+
}
855+
}

0 commit comments

Comments
 (0)