diff --git a/.github/workflows/contract_checks.yml b/.github/workflows/contract_checks.yml index 363999b..1649991 100644 --- a/.github/workflows/contract_checks.yml +++ b/.github/workflows/contract_checks.yml @@ -2,10 +2,19 @@ name: CI on: pull_request: + branches: + - dev + + push: + branches: + - dev + - main jobs: - check-lint: + determine-changes: runs-on: ubuntu-latest + outputs: + contracts_dir: ${{ steps.changes.outputs.contracts_dir }} steps: - uses: actions/checkout@v2 @@ -14,71 +23,76 @@ jobs: id: changes with: filters: | - contracts_dir: + contracts_dir: - 'contracts/src/**' - - name: Install scarb - if: steps.changes.outputs.contracts_dir == 'true' - run: | - cd contracts - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.6.5 + check-lint: + runs-on: ubuntu-latest + needs: determine-changes + steps: + - uses: actions/checkout@v2 + + - name: Setup Scarb + uses: software-mansion/setup-scarb@v1 + if: needs.determine-changes.outputs.contracts_dir == 'true' + with: + scarb-version: "2.6.5" - - name: Run scarb fmt - if: steps.changes.outputs.contracts_dir == 'true' - run: | - cd contracts - scarb fmt + - name: Run Scarb formatting + if: needs.determine-changes.outputs.contracts_dir == 'true' + working-directory: contracts + run: scarb fmt + + - name: No changes detected in contracts directory + if: needs.determine-changes.outputs.contracts_dir == 'false' + run: echo "No linting perfomed since no changes have been detected in the contracts directory." check-contracts-changes: runs-on: ubuntu-latest + needs: determine-changes steps: - uses: actions/checkout@v2 - - - name: Check for changes - uses: dorny/paths-filter@v3 - id: changes - with: - filters: | - contracts_dir: - - 'contracts/src/**' - name: Install scarb - if: steps.changes.outputs.contracts_dir == 'true' - run: | - cd contracts - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.6.5 + uses: software-mansion/setup-scarb@v1 + if: needs.determine-changes.outputs.contracts_dir == 'true' + with: + scarb-version: "2.6.5" + + - name: Run Scarb check + if: needs.determine-changes.outputs.contracts_dir == 'true' + working-directory: contracts + run: scarb check - - name: Run scarb check - if: steps.changes.outputs.contracts_dir == 'true' - run: | - cd contracts - scarb check + - name: No changes detected in contracts directory + if: needs.determine-changes.outputs.contracts_dir == 'false' + run: echo "No contracts checks done since no changes have been detected in the contracts directory." run-tests: runs-on: ubuntu-latest + needs: determine-changes steps: - name: Checkout repository uses: actions/checkout@v2 - - name: Check for changes - uses: dorny/paths-filter@v3 - id: changes + - name: Install Scarb + uses: software-mansion/setup-scarb@v1 + if: needs.determine-changes.outputs.contracts_dir == 'true' with: - filters: | - contracts_dir: - - 'contracts/src/**' + scarb-version: "2.6.5" - - name: Install scarb - if: steps.changes.outputs.contracts_dir == 'true' - run: | - cd contracts - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.6.5 - name: Install snfoundry uses: foundry-rs/setup-snfoundry@v3 + if: needs.determine-changes.outputs.contracts_dir == 'true' with: - starknet-foundry-version: "0.20.1" + starknet-foundry-version: "0.27.0" - name: Run Cairo tests + if: needs.determine-changes.outputs.contracts_dir == 'true' id: cairo_tests run: bash scripts/run_tests.sh + + - name: No changes detected in contracts directory + if: needs.determine-changes.outputs.contracts_dir == 'false' + run: echo "No tests were run since no changes have been detected in the contracts directory." diff --git a/README.md b/README.md index d058f8c..0afa46b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,12 @@ You can check the prototype we have in mind and suggest any improvement, if you - [Figma prototype](https://www.figma.com/design/SCiGViaoPT9UTrT7CsLbAt/Go-Stark-Me?node-id=0-1&t=FUhnIhNcXRIGkQ16-1) +## Contributors + +- [EmmanuelAR](https://github.com/EmmanuelAR) +- [adrianvrj](https://github.com/adrianvrj) +- [bitfalt](https://github.com/bitfalt) + ## Want to Contribute? If you are interested in contributing to Go Stark Me, please follow these steps: @@ -52,3 +58,5 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! + +- [@bitfalt](https://t.me/bitfalt) diff --git a/contracts/README.md b/contracts/README.md index ba60dfb..3467a71 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -1,16 +1,59 @@ # Go Stark Me - Backend Setup 💻 -### Steps to Run the Backend 🥳 +## Steps to Run the Backend 🥳 1. **Navigate to the Contracts Directory 🔍** + + Ensure you are in the correct directory where the Cairo contracts are stored. + ```bash cd gostarkme/contracts -2. **Compile gostarkme backend 🛠️** - ```bash - scarb build -3. **Run gostarkme unit test ✅** - ```bash - snforge test -4. **Run code formatter 📝** - ```bash - scarb fmt + ``` + +2. **Setup your environment** + + - Scarb v2.6.5 : [here](https://docs.swmansion.com/scarb/download.html#install-via-asdf). + ```bash + asdf install scarb 2.6.5 + ``` + ```bash + asdf global scarb 2.6.5 + ``` + - Starknet Foundry v0.27.0: [here](https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html). + ```bash + asdf install starknet-foundry 0.27.0 + ``` + ```bash + asdf install starknet-foundry 0.27.0 + ``` +3. **Compile Go Stark Me Backend 🛠️** + + To build the contracts, run the command: + + ```bash + scarb build + ``` + +4. **Run Go Stark Me Unit Tests ✅** + + To run the unit tests for the contracts, run the following command: + + ```bash + scarb run test + ``` + or + ```bash + snforge test + ``` + +5. **Run Code Formatter 📝** + + To format your contracts, simply run the command: + + ```bash + scarb fmt + ``` + +## UML Diagram + +![UML Class Diagram](https://github.com/user-attachments/assets/479c9296-e3ac-4ad3-bf79-5f458c456a45) \ No newline at end of file diff --git a/contracts/Scarb.lock b/contracts/Scarb.lock index 9079da6..7b2dd57 100644 --- a/contracts/Scarb.lock +++ b/contracts/Scarb.lock @@ -16,5 +16,5 @@ source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.10.0#d7 [[package]] name = "snforge_std" -version = "0.20.1" -source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.20.1#fea2db8f2b20148cc15ee34b08de12028eb42942" +version = "0.27.0" +source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.27.0#2d99b7c00678ef0363881ee0273550c44a9263de" diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index bfd50e9..539d2d6 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -10,7 +10,7 @@ starknet = "2.6.4" openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.10.0" } [dev-dependencies] -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.20.1" } +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.27.0" } [scripts] test = "snforge test" diff --git a/contracts/src/donator.cairo b/contracts/src/donator.cairo index c2c4faf..89fa525 100644 --- a/contracts/src/donator.cairo +++ b/contracts/src/donator.cairo @@ -52,7 +52,6 @@ mod Donator { fn getLevel(self: @ContractState) -> u32 { return self.level.read(); } - fn getTotalStarkDonations(self: @ContractState) -> u64 { return self.total_stark_donations.read(); } diff --git a/contracts/src/donatorManager.cairo b/contracts/src/donatorManager.cairo index 472d1be..5755655 100644 --- a/contracts/src/donatorManager.cairo +++ b/contracts/src/donatorManager.cairo @@ -4,6 +4,8 @@ use starknet::class_hash::ClassHash; #[starknet::interface] pub trait IDonatorManager { fn newDonator(ref self: TContractState); + fn getOwner(self: @TContractState) -> ContractAddress; + fn getDonatorClassHash(self: @TContractState) -> ClassHash; fn getDonatorByAddress(self: @TContractState, owner: ContractAddress) -> ContractAddress; } @@ -45,7 +47,6 @@ mod DonatorManager { impl DonatorManagerImpl of super::IDonatorManager { fn newDonator(ref self: ContractState) { let mut calldata = ArrayTrait::::new(); - calldata.append(get_caller_address().try_into().unwrap()); let (address_0, _) = deploy_syscall( @@ -54,6 +55,12 @@ mod DonatorManager { .unwrap(); self.donators.write(get_caller_address().try_into().unwrap(), address_0); } + fn getOwner(self: @ContractState) -> ContractAddress { + return self.owner.read(); + } + fn getDonatorClassHash(self: @ContractState) -> ClassHash { + return self.donator_class_hash.read(); + } fn getDonatorByAddress(self: @ContractState, owner: ContractAddress) -> ContractAddress { return self.donators.read(owner); } diff --git a/contracts/src/fundManager.cairo b/contracts/src/fundManager.cairo old mode 100644 new mode 100755 index 36426d8..7b815ac --- a/contracts/src/fundManager.cairo +++ b/contracts/src/fundManager.cairo @@ -6,13 +6,15 @@ pub trait IFundManager { fn newFund(ref self: TContractState, name: felt252, goal: u64); fn getCurrentId(self: @TContractState) -> u128; fn getFund(self: @TContractState, id: u128) -> ContractAddress; + fn getOwner(self: @TContractState) -> ContractAddress; + fn getFundClassHash(self: @TContractState) -> ClassHash; } #[starknet::contract] mod FundManager { - // ************************************************************************* + // *************************************************************************************** // IMPORT - // ************************************************************************* + // *************************************************************************************** use core::array::ArrayTrait; use core::traits::TryInto; use starknet::ContractAddress; @@ -20,9 +22,9 @@ mod FundManager { use starknet::class_hash::ClassHash; use starknet::get_caller_address; - // ************************************************************************* + // *************************************************************************************** // STORAGE - // ************************************************************************* + // *************************************************************************************** #[storage] struct Storage { owner: ContractAddress, @@ -31,9 +33,9 @@ mod FundManager { fund_class_hash: ClassHash, } - // ************************************************************************* + // *************************************************************************************** // CONSTRUCTOR - // ************************************************************************* + // *************************************************************************************** #[constructor] fn constructor(ref self: ContractState, fund_class_hash: felt252) { self.owner.write(get_caller_address()); @@ -41,9 +43,9 @@ mod FundManager { self.current_id.write(0); } - // ************************************************************************* + // *************************************************************************************** // EXTERNALS - // ************************************************************************* + // *************************************************************************************** #[abi(embed_v0)] impl FundManagerImpl of super::IFundManager { fn newFund(ref self: ContractState, name: felt252, goal: u64) { @@ -56,8 +58,8 @@ mod FundManager { self.fund_class_hash.read(), 12345, calldata.span(), false ) .unwrap(); - self.funds.write(self.current_id.read(), address_0); self.current_id.write(self.current_id.read() + 1); + self.funds.write(self.current_id.read(), address_0); } fn getCurrentId(self: @ContractState) -> u128 { return self.current_id.read(); @@ -65,5 +67,11 @@ mod FundManager { fn getFund(self: @ContractState, id: u128) -> ContractAddress { return self.funds.read(id); } + fn getOwner(self: @ContractState) -> ContractAddress { + return self.owner.read(); + } + fn getFundClassHash(self: @ContractState) -> ClassHash { + return self.fund_class_hash.read(); + } } -} +} \ No newline at end of file diff --git a/contracts/tests/test_donator.cairo b/contracts/tests/test_donator.cairo index 1402bd8..62d758e 100644 --- a/contracts/tests/test_donator.cairo +++ b/contracts/tests/test_donator.cairo @@ -14,10 +14,11 @@ fn OWNER() -> ContractAddress { } fn __setup__() -> ContractAddress { - let contract = declare("Donator"); + let contract = declare("Donator").unwrap(); let mut calldata: Array = array![]; calldata.append_serde(OWNER()); - contract.deploy(@calldata).unwrap() + let (address, _) = contract.deploy(@calldata).unwrap(); + address } // ************************************************************************* diff --git a/contracts/tests/test_donator_manager.cairo b/contracts/tests/test_donator_manager.cairo new file mode 100644 index 0000000..8fad032 --- /dev/null +++ b/contracts/tests/test_donator_manager.cairo @@ -0,0 +1,63 @@ +// ************************************************************************* +// DONATOR MANAGER TEST +// ************************************************************************* +use starknet::{ContractAddress, contract_address_const}; +use starknet::class_hash::{ClassHash}; +use starknet::syscalls::deploy_syscall; + +use snforge_std::{ + ContractClass, declare, ContractClassTrait, start_cheat_caller_address_global, get_class_hash +}; + +use openzeppelin::utils::serde::SerializedAppend; + +use gostarkme::donatorManager::IDonatorManagerDispatcher; +use gostarkme::donatorManager::IDonatorManagerDispatcherTrait; + +fn OWNER() -> ContractAddress { + contract_address_const::<'OWNER'>() +} + +fn __setup__() -> (ContractAddress, ClassHash) { + // Donator + let donator = declare("Donator").unwrap(); + let mut donator_calldata: Array = array![]; + donator_calldata.append_serde(OWNER()); + let (donator_contract_address, _) = donator.deploy(@donator_calldata).unwrap(); + let donator_class_hash = get_class_hash(donator_contract_address); + + // Donator Manager + let donator_manager = declare("DonatorManager").unwrap(); + let mut donator_manager_calldata: Array = array![]; + donator_manager_calldata.append_serde(donator_class_hash); + let (contract_address, _) = donator_manager.deploy(@donator_manager_calldata).unwrap(); + + return (contract_address, donator_class_hash,); +} + +// ************************************************************************* +// TEST +// ************************************************************************* +#[test] +fn test_constructor() { + // Put owner address like caller + start_cheat_caller_address_global(OWNER()); + let (contract_address, donator_class_hash) = __setup__(); + let donator_manager_contract = IDonatorManagerDispatcher { contract_address }; + let expected_donator_address = donator_manager_contract.getDonatorClassHash(); + let owner = donator_manager_contract.getOwner(); + assert(owner == OWNER(), 'Invalid owner'); + assert(donator_class_hash == expected_donator_address, 'Invalid donator class hash'); +} + +#[test] +fn test_new_donator() { + start_cheat_caller_address_global(OWNER()); + let (contract_address, donator_class_hash) = __setup__(); + let donator_manager_contract = IDonatorManagerDispatcher { contract_address }; + donator_manager_contract.newDonator(); + let expected_donator_class_hash = get_class_hash( + donator_manager_contract.getDonatorByAddress(OWNER()) + ); + assert(expected_donator_class_hash == donator_class_hash, 'Invalid donator address'); +} diff --git a/contracts/tests/test_fund.cairo b/contracts/tests/test_fund.cairo index 5c25946..db8e25d 100644 --- a/contracts/tests/test_fund.cairo +++ b/contracts/tests/test_fund.cairo @@ -1,9 +1,9 @@ -// ************************* +// *************************************************************************************** // FUND TEST -// ************************* +// *************************************************************************************** use starknet::{ContractAddress, contract_address_const}; -use snforge_std::{declare, ContractClassTrait, CheatTarget}; +use snforge_std::{declare, ContractClassTrait, start_cheat_caller_address_global}; use openzeppelin::utils::serde::SerializedAppend; @@ -29,17 +29,18 @@ fn GOAL() -> u64 { 1000 } fn _setup_() -> ContractAddress { - let contract = declare("Fund"); + let contract = declare("Fund").unwrap(); let mut calldata: Array = array![]; calldata.append_serde(ID()); calldata.append_serde(OWNER()); calldata.append_serde(NAME()); calldata.append_serde(GOAL()); - contract.deploy(@calldata).unwrap() + let (contract_address, _) = contract.deploy(@calldata).unwrap(); + contract_address } -// ************************* +// *************************************************************************************** // TEST -// ************************* +// *************************************************************************************** #[test] fn test_constructor() { let contract_address = _setup_(); @@ -68,7 +69,7 @@ fn test_set_name() { let dispatcher = IFundDispatcher { contract_address }; let name = dispatcher.getName(); assert(name == NAME(), 'Invalid name'); - snforge_std::start_prank(CheatTarget::One(contract_address), OWNER()); + start_cheat_caller_address_global(OWNER()); dispatcher.setName('NEW_NAME'); let new_name = dispatcher.getName(); assert(new_name == 'NEW_NAME', 'Set name method not working') @@ -80,7 +81,7 @@ fn test_set_reason() { let dispatcher = IFundDispatcher { contract_address }; let reason = dispatcher.getReason(); assert(reason == " ", 'Invalid reason'); - snforge_std::start_prank(CheatTarget::One(contract_address), OWNER()); + start_cheat_caller_address_global(OWNER()); dispatcher.setReason(REASON()); let new_reason = dispatcher.getReason(); assert(new_reason == REASON(), 'Set reason method not working') @@ -92,7 +93,7 @@ fn test_set_goal() { let dispatcher = IFundDispatcher { contract_address }; let goal = dispatcher.getGoal(); assert(goal == GOAL(), 'Invalid goal'); - snforge_std::start_prank(CheatTarget::One(contract_address), OWNER()); + start_cheat_caller_address_global(OWNER()); dispatcher.setGoal(123); let new_goal = dispatcher.getGoal(); assert(new_goal == 123, 'Set goal method not working') @@ -132,7 +133,7 @@ fn test_receive_donation_successful() { // Put state as recollecting dons dispatcher.setIsActive(2); // Put 10 strks as goal, only owner - snforge_std::start_prank(CheatTarget::One(contract_address), OWNER()); + start_cheat_caller_address_global(OWNER()); dispatcher.setGoal(10); // Donate 5 strks dispatcher.receiveDonation(5); diff --git a/contracts/tests/test_fund_manager.cairo b/contracts/tests/test_fund_manager.cairo new file mode 100755 index 0000000..25ae3c2 --- /dev/null +++ b/contracts/tests/test_fund_manager.cairo @@ -0,0 +1,83 @@ +// *************************************************************************************** +// FUND MANAGER TEST +// *************************************************************************************** +use starknet::{ContractAddress, contract_address_const}; +use starknet::class_hash::{ClassHash}; +use starknet::syscalls::deploy_syscall; + +use snforge_std::{ + ContractClass, declare, ContractClassTrait, start_cheat_caller_address_global, get_class_hash +}; + +use openzeppelin::utils::serde::SerializedAppend; + +use gostarkme::fundManager::IFundManagerDispatcher; +use gostarkme::fundManager::IFundManagerDispatcherTrait; + +fn ID() -> u128 { + 1 +} +fn OWNER() -> ContractAddress { + contract_address_const::<'OWNER'>() +} +fn OTHER_USER() -> ContractAddress { + contract_address_const::<'USER'>() +} +fn NAME() -> felt252 { + 'NAME_FUND_TEST' +} +fn REASON() -> ByteArray { + "Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum" +} +fn GOAL() -> u64 { + 1000 +} + +fn _setup_() -> (ContractAddress, ClassHash) { + // Fund + let fund = declare("Fund").unwrap(); + let mut fund_calldata: Array = array![]; + fund_calldata.append_serde(ID()); + fund_calldata.append_serde(OWNER()); + fund_calldata.append_serde(NAME()); + fund_calldata.append_serde(GOAL()); + let (fund_contract_address, _) = fund.deploy(@fund_calldata).unwrap(); + let fund_class_hash = get_class_hash(fund_contract_address); + + // Fund Manager + let fund_manager = declare("FundManager").unwrap(); + let mut fund_manager_calldata: Array = array![]; + fund_manager_calldata.append_serde(fund_class_hash); + let (contract_address, _) = fund_manager.deploy(@fund_manager_calldata).unwrap(); + + return (contract_address, fund_class_hash,); +} + +// ****************************************************************************** +// TEST +// ****************************************************************************** + +#[test] +fn test_constructor() { + start_cheat_caller_address_global(OWNER()); + let (contract_address, fund_class_hash) = _setup_(); + let fund_manager_contract = IFundManagerDispatcher { contract_address }; + let expected_fund_address = fund_manager_contract.getFundClassHash(); + let owner = fund_manager_contract.getOwner(); + assert(owner == OWNER(), 'Invalid owner'); + assert(fund_class_hash == expected_fund_address, 'Invalid fund class hash'); +} + +#[test] +fn test_new_fund(){ + start_cheat_caller_address_global(OWNER()); + let (contract_address, fund_class_hash) = _setup_(); + let fund_manager_contract = IFundManagerDispatcher { contract_address }; + fund_manager_contract.newFund(NAME(), GOAL()); + let expected_fund_class_hash = get_class_hash( + fund_manager_contract.getFund(1) + ); + let current_id = fund_manager_contract.getCurrentId(); + assert(expected_fund_class_hash == fund_class_hash, 'Invalid fund address'); + assert(current_id == 1, 'Invalid current ID'); +} \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index c403366..25a67a9 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,36 +1,79 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). -## Getting Started -First, run the development server: + +### Pre-requisites + +- Install Node.js (v20.15.1) using Node Version Manager (NVM) + + We recommend using [Node Version Manager (NVM) ](https://github.com/nvm-sh/nvm) to easily manage different versions of Node.js on your system. + +### Local configurations + + +- Rename the `frontend/gostarkme-web/.env.example` file to: `frontend/gostarkme-web/.env`. + + + +- Comment the content of the file `frontend/gostarkme-web/next.config.mjs`. + + +## Local Deployment + +### Installing Dependencies + +First, install the required packages: + +```bash +cd gostarkme/frontend/gostarkme-web + +npm install +``` +### Running the Application + + +Once the dependencies are installed, start the development server: + ```bash npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev ``` + + + Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. -## Learn More +## Production Deployment + -To learn more about Next.js, take a look at the following resources: +### Installing Dependencies + +First, install the required packages: + +```bash +cd gostarkme/frontend/gostarkme-web + +npm install +``` -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! +### Build the Application +To create an optimized production build, run: -## Deploy on Vercel -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +```bash +npm run build +``` + + +### Serve the build +Start the production server: +```bash +npm run start +``` + + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. diff --git a/frontend/gostarkme-web/.env b/frontend/gostarkme-web/.env new file mode 100644 index 0000000..0ec5e11 --- /dev/null +++ b/frontend/gostarkme-web/.env @@ -0,0 +1 @@ +NEXT_PUBLIC_APP_ROOT = "/" \ No newline at end of file diff --git a/frontend/gostarkme-web/.gitignore b/frontend/gostarkme-web/.gitignore index d13968e..66e9cfc 100644 --- a/frontend/gostarkme-web/.gitignore +++ b/frontend/gostarkme-web/.gitignore @@ -35,4 +35,9 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -/package-lock.json \ No newline at end of file +/package-lock.json +/package.json +/yarn.lock +/.env* +/.env.example +/next.config.mjs \ No newline at end of file diff --git a/frontend/gostarkme-web/animations/StardustAnimation.tsx b/frontend/gostarkme-web/animations/StardustAnimation.tsx index 807fe5f..1a83f5a 100644 --- a/frontend/gostarkme-web/animations/StardustAnimation.tsx +++ b/frontend/gostarkme-web/animations/StardustAnimation.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef } from 'react'; -export const StardustAnimation = () => { +export const StardustAnimation = ({height, width}: {height?: number, width?: number}) => { const canvasRef = useRef(null); useEffect(() => { @@ -11,8 +11,8 @@ export const StardustAnimation = () => { let animationFrameId: number; // Set canvas size - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; + canvas.width = width ?? window.innerWidth; + canvas.height = height ?? window.innerHeight; // Create stars const stars: { x: number; y: number; size: number; speed: number; }[] = []; @@ -54,7 +54,7 @@ export const StardustAnimation = () => { return () => { cancelAnimationFrame(animationFrameId); }; - }, []); + }, [height, width]); - return ; + return ; }; \ No newline at end of file diff --git a/frontend/gostarkme-web/app/app/fund/[fundId]/page.tsx b/frontend/gostarkme-web/app/app/fund/[fundId]/page.tsx new file mode 100644 index 0000000..dfa40c2 --- /dev/null +++ b/frontend/gostarkme-web/app/app/fund/[fundId]/page.tsx @@ -0,0 +1,21 @@ +import Fund from "@/components/modules/Fund/Fund"; +import Bounded from "@/components/ui/Bounded"; +import Divider from "@/components/ui/Divider"; + +export function generateStaticParams() { + return [{ fundId: '1' }] +} + +const FundDetailsPage = async ({ params }: { params: { fundId: string } }) => { + return ( + <> + +

User's fund - {params.fundId}

+ + +
+ + ); +}; + +export default FundDetailsPage; diff --git a/frontend/gostarkme-web/app/app/myfunds/page.tsx b/frontend/gostarkme-web/app/app/myfunds/page.tsx new file mode 100644 index 0000000..79ca7db --- /dev/null +++ b/frontend/gostarkme-web/app/app/myfunds/page.tsx @@ -0,0 +1,44 @@ +'use client' + +import UserFunds from '@/components/modules/myfunds/UserFunds'; +import Navbar from '@/components/ui/Navbar'; +import Footer from '@/components/ui/Footer'; +import { useState } from 'react'; +import { useEventListener, useLocalStorage } from 'usehooks-ts' + +const MyFundsPage = () => { + const [storedAddress, setStoredAddress] = useState(typeof window !== 'undefined' ? localStorage.getItem('walletAddress') : null); + + const handleWalletChange = () => { + const addr = localStorage.getItem("walletAddress"); + setStoredAddress(addr); + } + + useEventListener("local-storage", handleWalletChange); + + const navItems = [ + { label: 'My Profile', href: `/app/myprofile` }, + { label: 'My funds', href: `/app/myfunds` } + ]; + + return ( +
+ +
+ +
+
+
+ ); +}; + +export default MyFundsPage; \ No newline at end of file diff --git a/frontend/gostarkme-web/app/app/myprofile/page.tsx b/frontend/gostarkme-web/app/app/myprofile/page.tsx new file mode 100644 index 0000000..5178fe1 --- /dev/null +++ b/frontend/gostarkme-web/app/app/myprofile/page.tsx @@ -0,0 +1,112 @@ +'use client' + +import ProgressBar from '@/components/ui/ProgressBar'; +import Divider from '@/components/ui/Divider'; +import Image from 'next/image'; +import Footer from '@/components/ui/Footer'; +import Navbar from '@/components/ui/Navbar'; +import { useEventListener } from 'usehooks-ts'; +import { useState } from 'react'; + +const UserProfilePage = () => { + + const [storedAddress, setStoredAddress] = useState(typeof window !== 'undefined' ? localStorage.getItem('walletAddress') : null); + + const handleWalletChange = () => { + const addr = localStorage.getItem("walletAddress"); + setStoredAddress(addr); + } + + useEventListener("local-storage", handleWalletChange); + + const navItems = [ + { label: 'My Profile', href: `/app/myprofile` }, + { label: 'My funds', href: `/app/myfunds` } + ]; + + // Mock data for design purposes + const totalDonations = 20000; + const currentLevel = 10; + const currentPoints = 200; + const totalPoints = 500; + + // Calculate progress percentage + const progress = (currentPoints / totalPoints) * 100; + + return ( +
+ + {storedAddress !== null ? ( +
+ {/* Profile Section */} +
+ {/* Profile Header */} +

+ + {storedAddress.slice(0, 5)}...{storedAddress.slice(-4)} + + {"'s Profile "} {'\u2728'} +

+ + + + {/* Total Donations and Current Level */} +

+ Total donations: + + {totalDonations.toLocaleString()} + + STRKs +

+ +

+ Current level: {currentLevel} +

+

+ Your progress to next level +

+ + + + + +
+ {currentPoints} / {totalPoints} + STRKs +
+
+
+ ) : ( +
+
+ Please connect your wallet to see your profile. +
+
+ )} +
+
+ ); +}; + +export default UserProfilePage; diff --git a/frontend/gostarkme-web/app/app/newfunding/page.tsx b/frontend/gostarkme-web/app/app/newfunding/page.tsx new file mode 100644 index 0000000..3cbfd5d --- /dev/null +++ b/frontend/gostarkme-web/app/app/newfunding/page.tsx @@ -0,0 +1,20 @@ +"use client"; // Mark this component as a Client Component +import Bounded from "@/components/ui/Bounded"; +import Divider from "@/components/ui/Divider"; +import Stages from "@/components/modules/newfunding/Stages"; + +const NewFundingPage = ({ params }: { params: { fundId: string } }) => { + return ( + <> + +

+ Your new fund! +

+ + +
+ + ); +}; + +export default NewFundingPage; diff --git a/frontend/gostarkme-web/app/app/page.tsx b/frontend/gostarkme-web/app/app/page.tsx new file mode 100644 index 0000000..855a557 --- /dev/null +++ b/frontend/gostarkme-web/app/app/page.tsx @@ -0,0 +1,71 @@ +import FundCards from "@/components/dashboard/fundCard"; +import Footer from "@/components/ui/Footer"; +import Navbar from "@/components/ui/Navbar"; +import React from "react"; + +const Dashboard = () => { + const navItems = [ + { label: 'My Profile', href: 'app/myprofile' }, + { label: 'My funds', href: '/app/myfunds' } + ]; + + const funds = [ + { + type: "Project", + title: "Adrian's fund", + description: "I need help with my project", + }, + { + type: "Charity", + title: "Adrian's fund", + description: "I need help with my project", + }, + { + type: "Charity", + title: "Adrian's fund", + description: "I need help with my project", + }, + { + type: "Project", + title: "Adrian's fund", + description: "I need help with my project", + }, + + { + type: "Charity", + title: "Adrian's fund", + description: "I need help with my project", + }, + { + type: "Project", + title: "Adrian's fund", + description: "I need help with my project", + }, + ]; + return ( +
+ +

+ Latest Funds + +

+
+ {funds.map((fund, index) => ( + + ))} +
+
+
+ ); +}; + +export default Dashboard; diff --git a/frontend/gostarkme-web/app/page.tsx b/frontend/gostarkme-web/app/page.tsx index c0b2ac4..4bd231f 100644 --- a/frontend/gostarkme-web/app/page.tsx +++ b/frontend/gostarkme-web/app/page.tsx @@ -2,15 +2,20 @@ import { LinkButton } from "@/components/ui/LinkButton"; import { WelcomeBar } from "@/components/welcomepage/WelcomeBar"; import { WelcomeItems } from "@/components/welcomepage/WelcomeItems"; +import Footer from "@/components/ui/Footer"; import Image from "next/image"; import { StardustAnimation } from "@/animations/StardustAnimation"; +import useComponentSize from "@/hooks/useComponentSize.hook"; export default function Home() { const ROOT = process.env.NEXT_PUBLIC_APP_ROOT; - + + const [ref, width, height] = useComponentSize(); + return ( -
+
+

Upload your cause

@@ -18,8 +23,7 @@ export default function Home() { -
- +
stark logo - + +
{/* */}
@@ -102,6 +107,7 @@ export default function Home() {
+
); } diff --git a/frontend/gostarkme-web/components/dashboard/fundCard.tsx b/frontend/gostarkme-web/components/dashboard/fundCard.tsx new file mode 100644 index 0000000..e1f5ecf --- /dev/null +++ b/frontend/gostarkme-web/components/dashboard/fundCard.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { StardustAnimation } from "@/animations/StardustAnimation"; +import useComponentSize from "@/hooks/useComponentSize.hook"; +import React from "react"; + +interface FundCardProps { + fund: { + type: string; + title: string; + description: string; + }; + index: number; +} + +const FundCards = ({ fund, index }: FundCardProps) => { + const [ref, width, height] = useComponentSize(); + return ( +
+
+
+

+ {fund.type} {fund.type === "Project" ? 🚀 : 🫀} +

+

+ {fund.title} +

+
+
+

+ {fund.description} +

+
+ +
+
+ ); +}; + +export default FundCards; diff --git a/frontend/gostarkme-web/components/modules/Fund/Fund.tsx b/frontend/gostarkme-web/components/modules/Fund/Fund.tsx new file mode 100644 index 0000000..ccfa094 --- /dev/null +++ b/frontend/gostarkme-web/components/modules/Fund/Fund.tsx @@ -0,0 +1,25 @@ +"use client"; + +import FundDonate from "./FundDonate"; +import starknetlogo from "@/public/icons/starklogo.png"; +import { FundVote } from "./FundVote"; +import { useState } from "react"; + +interface FundProps { + message: string; +} + +const Fund = ({ message }: FundProps) => { + const [type, setType] = useState("donate"); + + return ( +
+

{message}

+ + {type === "donate" ? : } + {/* For Vote, there is no logo, but when you already have it, just pass it through the prop */} +
+ ); +}; + +export default Fund; diff --git a/frontend/gostarkme-web/components/modules/Fund/FundDonate.tsx b/frontend/gostarkme-web/components/modules/Fund/FundDonate.tsx new file mode 100644 index 0000000..a6c41d3 --- /dev/null +++ b/frontend/gostarkme-web/components/modules/Fund/FundDonate.tsx @@ -0,0 +1,70 @@ +"use client"; + +import ProgressBar from "@/components/ui/ProgressBar"; +import Image, { StaticImageData } from "next/image"; +import { useState } from "react"; + +interface FundDonateProps { + icon?: StaticImageData; +} + +const FundDonate = ({ icon }: FundDonateProps) => { + const [amount, setAmount] = useState(""); + const [error, setError] = useState(""); + + const handleAmountChange = (e: React.ChangeEvent) => { + const value = e.target.value === "" ? "" : Number(e.target.value); + setAmount(value); + setError(""); + }; + + const handleDonateClick = (e: React.MouseEvent) => { + e.preventDefault(); + if (amount === "") { + setError("This field is required."); + } else if (typeof amount === "number" && amount < 0) { + setError("The amount cannot be negative."); + } else { + console.log("Donating:", amount); + setError(""); + setTimeout(() => { + window.location.href = "/"; + }, 0); + } + }; + + + return ( +
+ +
+

200 / 300

+ icon +
+
+ +
+ {error && ( +

{error}

+ )} + +
+ ); +}; + +export default FundDonate; diff --git a/frontend/gostarkme-web/components/modules/Fund/FundVote.tsx b/frontend/gostarkme-web/components/modules/Fund/FundVote.tsx new file mode 100644 index 0000000..f4e543c --- /dev/null +++ b/frontend/gostarkme-web/components/modules/Fund/FundVote.tsx @@ -0,0 +1,21 @@ +import { LinkButton } from "@/components/ui/LinkButton"; +import ProgressBar from "@/components/ui/ProgressBar"; +import Image, { StaticImageData } from "next/image"; + +interface FundVoteProps { + icon?: StaticImageData; +} + +export const FundVote = ({ icon }: FundVoteProps) => { + return ( +
+ +
+

200 / 300

+ {/* icon */} +

🌟

+
+ +
+ ); +}; diff --git a/frontend/gostarkme-web/components/modules/myfunds/FundCard.tsx b/frontend/gostarkme-web/components/modules/myfunds/FundCard.tsx new file mode 100644 index 0000000..52e0439 --- /dev/null +++ b/frontend/gostarkme-web/components/modules/myfunds/FundCard.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { StardustAnimation } from '@/animations/StardustAnimation'; + +type FundType = 'Project' | 'Fund'; + +interface FundCardProps { + type: FundType; + title: string; + description: string; + onClick?: () => void; +} + +const FundCard: React.FC = ({ type, title, description, onClick }) => { + const fundEmoji = type === "Project" ? '🧠' : "🫀" + + return ( +
+
+ +
+
+
+ + {type} + + + {onClick && ( + + )} +
+

{title}

+
+

{description}

+
+
+
+ ); +}; + +export default FundCard; \ No newline at end of file diff --git a/frontend/gostarkme-web/components/modules/myfunds/UserFunds.tsx b/frontend/gostarkme-web/components/modules/myfunds/UserFunds.tsx new file mode 100644 index 0000000..030c07d --- /dev/null +++ b/frontend/gostarkme-web/components/modules/myfunds/UserFunds.tsx @@ -0,0 +1,95 @@ +"use client"; + +import React, { useEffect, useState } from 'react'; +import FundCard from '@/components/modules/myfunds/FundCard'; +import { Button } from '@/components/ui/Button'; +import { LinkButton } from '@/components/ui/LinkButton'; + +interface UserFundsProps { + userAddress: string | null; +} + +const UserFunds: React.FC = ({ userAddress }) => { + const [funds, setFunds] = useState([]); + + useEffect(() => { + // TODO: Implement funds fetching using 'userAddress' + setFunds([ + { + id: 1, + type: "Project", + title: "Batman's fund", + description: "Example of card without delete button", + }, + { + id: 2, + type: "Project", + title: "Deadpool's fund", + description: "I need help with my project to develop an awesome project like Go Stark Me", + onClick: handleDeleteFund + }, + { + id: 3, + type: "Fund", + title: "Spider-Man's fund", + description: "Text to prove that we add elipsis when text exceds card width and we don't break the layout. asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf", + onClick: handleDeleteFund + } + ]); + }, []); + + const handleDeleteFund = (fundId: number) => { + // TODO: Implement fund deletion based on a unique id or receive the fund object and delete it + alert(`Deleting fund with id: ${fundId}`); + } + + const onNewFundHandler = () => { + // TODO: Implement new fund action + alert(`Creating new fund`); + } + + return ( +
+
+
+

My Funds ✨

+
+ {userAddress !== null ? ( + + ) : null} +
+ + {userAddress === null ? ( +
+
+ Please connect your wallet to see your funds. +
+
+ ) : null} + + {funds.length === 0 && userAddress !== null ? ( +
+
+ No funds found for address {userAddress.slice(0, 5)}...{userAddress.slice(-4)} +
+
+ ) : null} + + {funds.length !== 0 && userAddress !== null ? ( +
+ {funds.map((fund: any, index: number) => ( + fund.onClick(fund.id) }} + /> + ))} +
+ ) : null} +
+ ); +}; + +export default UserFunds; diff --git a/frontend/gostarkme-web/components/modules/newfunding/DescriptionStep.tsx b/frontend/gostarkme-web/components/modules/newfunding/DescriptionStep.tsx new file mode 100644 index 0000000..0cd65d8 --- /dev/null +++ b/frontend/gostarkme-web/components/modules/newfunding/DescriptionStep.tsx @@ -0,0 +1,37 @@ +import React from "react"; + +interface DescriptionStepProps { + fundingDescription: string; + setFundingDescription: (description: string) => void; +} + +const DescriptionStep: React.FC = ({ + fundingDescription, + setFundingDescription, +}) => ( +
+ Note: You can always edit your funding description later ;) + +