All the features in one app
diff --git a/dapp/src/pages/Landing.tsx b/dapp/src/pages/Landing.tsx
new file mode 100644
index 0000000..ee7226a
--- /dev/null
+++ b/dapp/src/pages/Landing.tsx
@@ -0,0 +1,220 @@
+import React, { useState } from "react";
+import { motion } from "framer-motion";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+} from "@/components/ui/card";
+import { ChevronRight, Coins, Users, Zap } from "lucide-react";
+import { useAuth } from "@/hooks/use-auth";
+import { Input } from "@/components/ui/input";
+const Landing = () => {
+ const {
+ isAuthenticated,
+ connection,
+ userAddress,
+ bundlerKey,
+ register,
+ signIn,
+ signOut,
+ initializeBundler,
+ } = useAuth();
+ const [isRegistering, setIsRegistering] = useState(false);
+ const [username, setUsername] = useState("");
+
+ const handleRegister = (e: React.FormEvent) => {
+ e.preventDefault();
+ console.log("Registering with username:", username);
+ register();
+ };
+
+ const handleWalletLogin = () => {
+ console.log("Logging in with wallet");
+ signIn();
+ };
+
+ const containerVariants = {
+ hidden: { opacity: 0 },
+ visible: {
+ opacity: 1,
+ transition: { staggerChildren: 0.1 },
+ },
+ };
+
+ const itemVariants = {
+ hidden: { y: 20, opacity: 0 },
+ visible: {
+ y: 0,
+ opacity: 1,
+ transition: { type: "spring", stiffness: 100 },
+ },
+ };
+
+ return (
+
+ {/*
*/}
+
+ {/* Background Image Overlay */}
+
+
+
+
+
+
+
+ SplitterTab
+
+
+ Split expenses with crypto, revolutionize your finances
+
+
+
+
+ {isRegistering ? "Register" : "Login"}
+
+
+ Choose your authentication method
+
+
+
+
+ {isRegistering ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ About SplitterTab
+
+
+
+
+ SplitterTab is the ultimate solution for splitting expenses
+ using cryptocurrencies. Whether you're traveling with friends,
+ sharing a house, or managing group purchases, CryptoSplit makes
+ it easy to keep track of expenses and settle debts using your
+ favorite cryptocurrencies.
+
+
+
+
+
+
+
+ Features
+
+ {[
+ {
+ icon: Coins,
+ title: "Multi-Crypto Support",
+ description: "Split expenses using various cryptocurrencies",
+ },
+ {
+ icon: Users,
+ title: "Group Management",
+ description: "Create and manage expense groups easily",
+ },
+ {
+ icon: Zap,
+ title: "Passkey Support",
+ description: "Settle debts quickly with Passkey-powered wallets",
+ },
+ ].map((feature, index) => (
+
+
+
+
+
+
+ {feature.title}
+
+
+ {feature.description}
+
+
+
+
+
+ ))}
+
+
+
+ Built for EasyA Stellar London hackathon
+
+
+
+ );
+};
+
+export default Landing;
diff --git a/dapp/src/pages/Profile.tsx b/dapp/src/pages/Profile.tsx
index 5a7573e..7944387 100644
--- a/dapp/src/pages/Profile.tsx
+++ b/dapp/src/pages/Profile.tsx
@@ -2,6 +2,17 @@ import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
+import { motion, AnimatePresence } from "framer-motion";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { Progress } from "@/components/ui/progress";
+import { Check, X } from "lucide-react";
type Group = {
id: number;
@@ -11,7 +22,25 @@ type Group = {
isCreator: boolean;
};
+interface Wallet {
+ id: number;
+ user: string;
+ amount: number;
+ completed: boolean;
+}
+
+interface ExpenseDetails {
+ id: number;
+ organizer: string;
+ totalAmount: number;
+ wallets: Wallet[];
+}
+
+const MotionCard = motion(Card);
+const MotionButton = motion(Button);
+
export default function Profile() {
+ const [isOpen, setIsOpen] = useState(false);
const [groups, setGroups] = useState
([
{
id: 1,
@@ -30,6 +59,25 @@ export default function Profile() {
]);
const { toast } = useToast();
+ const expenseDetails: ExpenseDetails = {
+ id: 1,
+ organizer: "John Doe",
+ totalAmount: 1000,
+ wallets: [
+ { id: 1, user: "Alice", amount: 250, completed: true },
+ { id: 2, user: "Bob", amount: 250, completed: false },
+ { id: 3, user: "Charlie", amount: 250, completed: true },
+ { id: 4, user: "David", amount: 250, completed: false },
+ ],
+ };
+
+ const totalPaid = expenseDetails.wallets.reduce(
+ (sum, wallet) => (wallet.completed ? sum + wallet.amount : sum),
+ 0
+ );
+
+ const progressPercentage = (totalPaid / expenseDetails.totalAmount) * 100;
+
const handlePayment = (groupId: number) => {
setGroups(
groups.map((group) => {
@@ -45,56 +93,192 @@ export default function Profile() {
});
};
- const handleWithdraw = (groupId: number) => {
- toast({
- title: "Withdrawal Initiated",
- description: "Your withdrawal request has been submitted.",
- });
- };
+ const handleWithdraw = () =>
+ // groupId: number
+ {
+ toast({
+ title: "Withdrawal Initiated",
+ description: "Your withdrawal request has been submitted.",
+ });
+ };
return (
-
-
Your Expense Groups
- {groups.map((group) => (
-
-
- {group.name}
-
-
-
- Total Amount: ${group.totalAmount}
- Paid Amount: ${group.paidAmount}
-
-
- {group.isCreator ? (
-
- ) : (
-
- )}
-
-
- ))}
-
+
+
+
);
}
diff --git a/dapp/src/pages/Settings.tsx b/dapp/src/pages/Settings.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/dapp/src/utils.ts b/dapp/src/utils.ts
index ec2e0c3..2f3c61c 100644
--- a/dapp/src/utils.ts
+++ b/dapp/src/utils.ts
@@ -1,3 +1,89 @@
+import axios from "axios";
+import { SorobanRpc } from "@stellar/stellar-sdk";
+import {
+ Keypair,
+ xdr,
+ Address,
+ Operation,
+ TransactionBuilder,
+ Account,
+ scValToNative,
+} from "@stellar/stellar-sdk";
+import { splitterContract } from "./constants";
+
+export const contractRead = async (
+ bundlerKey: Keypair,
+ // accountContractId: string,
+ functionName: string,
+ functionArgs: any[]
+) => {
+ const key = bundlerKey;
+
+ const op = Operation.invokeContractFunction({
+ contract: splitterContract,
+ function: functionName, //"votes",
+ args: functionArgs,
+ // [
+ // xdr.ScVal.scvAddress(Address.fromString(accountContractId).toScAddress()),
+ // ],
+ });
+
+ const transaction = new TransactionBuilder(
+ new Account(key.publicKey(), "0"),
+ {
+ fee: "0",
+ networkPassphrase: import.meta.env.VITE_PUBLIC_networkPassphrase,
+ }
+ )
+ .addOperation(op)
+ .setTimeout(0)
+ .build();
+
+ const rpc = new SorobanRpc.Server(import.meta.env.VITE_PUBLIC_rpcUrl);
+
+ const simResp = await rpc.simulateTransaction(transaction);
+
+ if (!SorobanRpc.Api.isSimulationSuccess(simResp)) {
+ throw simResp;
+ } else {
+ return simResp;
+ }
+};
+
+export const fetchGroups = async () => {};
+
+export const uploadImageToInfura = async (imageFile: any) => {
+ const formData = new FormData();
+ formData.append("file", imageFile);
+
+ const infuraProjectId = import.meta.env.VITE_PUBLIC_INFURA_ID;
+ const infuraProjectSecret = import.meta.env.VITE_PUBLIC_INFURA_SECRET;
+
+ console.log(infuraProjectId, `\n`, infuraProjectSecret);
+
+ try {
+ const response = await axios.post(
+ "https://ipfs.infura.io:5001/api/v0/add",
+ formData,
+ {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ Authorization:
+ "Basic " +
+ Buffer.from(infuraProjectId + ":" + infuraProjectSecret).toString(
+ "base64"
+ ),
+ },
+ }
+ );
+
+ console.log("Upload successful:", response.data);
+ return response.data.Hash;
+ } catch (error) {
+ console.error("Error uploading to Infura:", error);
+ throw error;
+ }
+};
export function truncateStr(str: string, n = 6) {
if (!str) return "";
return str.length > n
diff --git a/dapp/src/web.ts b/dapp/src/web.ts
index 52b7496..912dd42 100644
--- a/dapp/src/web.ts
+++ b/dapp/src/web.ts
@@ -1,48 +1,63 @@
-import { WebPlugin } from '@capacitor/core';
-import { browserSupportsWebAuthn, browserSupportsWebAuthnAutofill, startAuthentication, startRegistration } from '@simplewebauthn/browser';
-import { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON } from '@simplewebauthn/types';
-
-import type { WebAuthnPlugin } from './definitions';
+import { WebPlugin } from "@capacitor/core";
+import {
+ browserSupportsWebAuthn,
+ browserSupportsWebAuthnAutofill,
+ startAuthentication,
+ startRegistration,
+} from "@simplewebauthn/browser";
+import {
+ AuthenticationResponseJSON,
+ PublicKeyCredentialCreationOptionsJSON,
+ PublicKeyCredentialRequestOptionsJSON,
+ RegistrationResponseJSON,
+} from "@simplewebauthn/types";
+
+import type { WebAuthnPlugin } from "./definitions";
export class WebAuthnWeb extends WebPlugin implements WebAuthnPlugin {
-
- async startRegistration(publicKeyCredentialCreationOptionsJSON: PublicKeyCredentialCreationOptionsJSON): Promise {
+ async startRegistration(
+ publicKeyCredentialCreationOptionsJSON: PublicKeyCredentialCreationOptionsJSON
+ ): Promise {
let res;
try {
res = await startRegistration(publicKeyCredentialCreationOptionsJSON);
} catch (error) {
- return Promise.reject(error)
+ return Promise.reject(error);
}
- return Promise.resolve(res)
+ return Promise.resolve(res);
}
- async startAuthentication(requestOptionsJSON: PublicKeyCredentialRequestOptionsJSON, useBrowserAutofill?: boolean): Promise {
+ async startAuthentication(
+ requestOptionsJSON: PublicKeyCredentialRequestOptionsJSON,
+ useBrowserAutofill?: boolean
+ ): Promise {
let res;
try {
res = await startAuthentication(requestOptionsJSON, useBrowserAutofill);
} catch (error) {
- return Promise.reject(error)
+ return Promise.reject(error);
}
- return Promise.resolve(res)
+ return Promise.resolve(res);
}
async isWebAuthnAvailable(): Promise<{ value: boolean }> {
- return this.isAvailable('webauthn');
+ return this.isAvailable("webauthn");
}
async isWebAuthnAutoFillAvailable(): Promise<{ value: boolean }> {
- return this.isAvailable('webauthnautofill');
+ return this.isAvailable("webauthnautofill");
}
- private async isAvailable(type: 'webauthn' | 'webauthnautofill'): Promise<{ value: boolean }> {
+ private async isAvailable(
+ type: "webauthn" | "webauthnautofill"
+ ): Promise<{ value: boolean }> {
let val = false;
- if (type === 'webauthn') {
+ if (type === "webauthn") {
val = await browserSupportsWebAuthn();
}
- if (type === 'webauthnautofill') {
+ if (type === "webauthnautofill") {
val = await browserSupportsWebAuthnAutofill();
}
return Promise.resolve({ value: val });
}
-
}