Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add localtunnel, auto-network switch #6

Merged
merged 7 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,30 @@ jobs:
- uses: actions/setup-node@v3 # Latest version as of now
with:
node-version: '22.12.0'
cache: 'npm'
cache: 'yarn'

- name: ⚙️ Install dependencies
run: npm install
run: yarn install

- name: Build
run: |
npx @cloudflare/next-on-pages
yarn dlx @cloudflare/next-on-pages

- name: 'Deploy release'
if: ${{ steps.extract_branch.outputs.branch_name == '' }}
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
run: |
npx wrangler pages deploy --project-name "${{ secrets.CF_PROJECT_NAME }}" .vercel/output/static
yarn dlx wrangler pages deploy --project-name "${{ secrets.CF_PROJECT_NAME }}" .vercel/output/static

- name: Deploy ${{ steps.extract_branch.outputs.branch_name }} (PR)
if: ${{ steps.extract_branch.outputs.branch_name != '' }}
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
run: |
npx wrangler pages deploy --project-name "${{ secrets.CF_PROJECT_NAME }}" --branch "${{ steps.extract_branch.outputs.branch_name }}" .vercel/output/static | tee output.log
yarn dlx wrangler pages deploy --project-name "${{ secrets.CF_PROJECT_NAME }}" --branch "${{ steps.extract_branch.outputs.branch_name }}" .vercel/output/static | tee output.log
sed < output.log -n 's#.*Take a peek over at \(.*\)$#specific_url=\1#p' >> $GITHUB_OUTPUT
id: deploy

Expand Down
934 changes: 934 additions & 0 deletions .yarn/releases/yarn-4.6.0.cjs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.6.0.cjs
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This template showcases:
- Integrates the [@lukso/web-components](https://www.npmjs.com/package/@lukso/web-components) library for ready-to-use branded components
- Uses the [erc725js](https://docs.lukso.tech/tools/dapps/erc725js/getting-started) library to fetch profile data from the blockchain

> **Cursor Tip:** You can rename this README.md file to `repo.cursorrules` for better AI development experience using Cursor.

## Key Features

### UP-Provider Integration
Expand Down Expand Up @@ -44,6 +46,26 @@ yarn dev

3. Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.(Note that the Grid context is not available in the local environment)

4. Testing your mini-app on the Grid:

We're using `localtunnel` to test the mini-app on the Grid. This library helps us to generate a public URL that can be used to add the mini-app to the Grid.

> Alternatively, you can use free cloud deployment services like Vercel, Replit, etc.

Globally install `localtunnel`:

```bash
npm install -g localtunnel
```

In the second terminal, run:

```bash
lt --port <LOCALHOST_PORT>
```

You can use this URL to add the mini-app to the Grid.

## Project Structure

- `components/upProvider.tsx`: Core UP Provider implementation and wallet connection logic
Expand Down
38 changes: 28 additions & 10 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
'use client';
"use client";

import { UpProvider } from '@/components/upProvider';
import { Donate } from '@/components/Donate';
import { ProfileSearch } from '@/components/ProfileSearch';
import { useUpProvider } from '@/components/upProvider';
import { UpProvider } from "@/components/upProvider";
import { Donate } from "@/components/Donate";
import { ProfileSearch } from "@/components/ProfileSearch";
import { useUpProvider } from "@/components/upProvider";
import { useState, useEffect } from "react";

// Import the LUKSO web-components library
import('@lukso/web-components');
let promise: Promise<unknown> | null = null;
if (typeof window !== "undefined") {
promise = import("@lukso/web-components");
}

/**
* Main content component that handles the conditional rendering of Donate and ProfileSearch components.
* Utilizes the UpProvider context to manage selected addresses and search state.
*
*
* @component
* @returns {JSX.Element} A component that toggles between Donate and ProfileSearch views
* based on the isSearching state from UpProvider.
*/
function MainContent() {
const [mounted, setMounted] = useState(false);

useEffect(() => {
// Load web component here if needed
promise?.then(() => {
setMounted(true);
});
}, []);

const { selectedAddress, setSelectedAddress, isSearching } = useUpProvider();

if (!mounted) {
return null; // or a loading placeholder
}

return (
<>
<div className={`${isSearching ? 'hidden' : 'block'}`}>
<div className={`${isSearching ? "hidden" : "block"}`}>
<Donate selectedAddress={selectedAddress} />
</div>

<div className={`${!isSearching ? 'hidden' : 'block'}`}>
<div className={`${!isSearching ? "hidden" : "block"}`}>
<ProfileSearch onSelectAddress={setSelectedAddress} />
</div>
</>
Expand All @@ -34,7 +52,7 @@ function MainContent() {
/**
* Root component of the application that wraps the main content with the UpProvider context.
* Serves as the entry point for the donation and profile search functionality.
*
*
* @component
* @returns {JSX.Element} The wrapped MainContent component with UpProvider context
*/
Expand Down
30 changes: 24 additions & 6 deletions components/Donate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function Donate({ selectedAddress }: DonateProps) {
const [amount, setAmount] = useState<number>(minAmount);
const [error, setError] = useState('');
const recipientAddress = selectedAddress || contextAccounts[0];
const [isLoading, setIsLoading] = useState(false);

const validateAmount = useCallback((value: number) => {
if (value < minAmount) {
Expand All @@ -53,12 +54,28 @@ export function Donate({ selectedAddress }: DonateProps) {
}, [amount, validateAmount]);

const sendToken = async () => {
if (!client || !walletConnected || !amount) return;
await client.sendTransaction({
account: accounts[0] as `0x${string}`,
to: recipientAddress as `0x${string}`,
value: parseUnits(amount.toString(), 18),
});
if (!client || !walletConnected || !amount) {
return;
}

try {
setIsLoading(true);
const tx = await client.sendTransaction({
account: accounts[0] as `0x${string}`,
to: recipientAddress as `0x${string}`,
value: parseUnits(amount.toString(), 18),
});

// Wait for transaction confirmation
await client.waitForTransactionReceipt({ hash: tx });

// Reset amount after successful transaction
setAmount(minAmount);
} catch (err) {
console.error('Transaction failed:', err);
} finally {
setIsLoading(false);
}
};

const handleOnInput = useCallback(
Expand Down Expand Up @@ -99,6 +116,7 @@ export function Donate({ selectedAddress }: DonateProps) {
variant="primary"
size="medium"
className="mt-2"
isLoading={isLoading}
disabled={!walletConnected}
>
{`Donate ${amount} LYX`}
Expand Down
14 changes: 8 additions & 6 deletions components/LuksoProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ import { useUpProvider } from './upProvider';

// Constants for the IPFS gateway and RPC endpoint for the LUKSO testnet
const IPFS_GATEWAY = 'https://api.universalprofile.cloud/ipfs/';
const RPC_ENDPOINT = 'https://rpc.testnet.lukso.network';
const RPC_ENDPOINT_TESTNET = 'https://rpc.testnet.lukso.network';
const RPC_ENDPOINT_MAINNET = 'https://rpc.mainnet.lukso.network';

interface LuksoProfileProps {
address: string;
}

export function LuksoProfile({ address }: LuksoProfileProps) {
const { setIsSearching } = useUpProvider();
const { setIsSearching, chainId } = useUpProvider();
const [profileData, setProfileData] = useState<{
imgUrl: string;
fullName: string;
Expand All @@ -40,7 +41,7 @@ export function LuksoProfile({ address }: LuksoProfileProps) {
imgUrl: 'https://tools-web-components.pages.dev/images/sample-avatar.jpg',
fullName: 'username',
background: 'https://tools-web-components.pages.dev/images/sample-background.jpg',
profileAddress: '0x12345',
profileAddress: '0x1234567890111213141516171819202122232425',
isLoading: false,
});

Expand All @@ -52,7 +53,8 @@ export function LuksoProfile({ address }: LuksoProfileProps) {

try {
const config = { ipfsGateway: IPFS_GATEWAY };
const profile = new ERC725(erc725schema, address, RPC_ENDPOINT, config);
const rpcEndpoint = chainId === 42 ? RPC_ENDPOINT_MAINNET : RPC_ENDPOINT_TESTNET;
const profile = new ERC725(erc725schema, address, rpcEndpoint, config);
const fetchedData = await profile.fetchData('LSP3Profile');

if (
Expand Down Expand Up @@ -86,7 +88,7 @@ export function LuksoProfile({ address }: LuksoProfileProps) {
}

fetchProfileImage();
}, [address]);
}, [address, chainId]);

return (
<lukso-card
Expand Down Expand Up @@ -115,7 +117,7 @@ export function LuksoProfile({ address }: LuksoProfileProps) {
size="small"
isIcon={true}
>
<lukso-icon name="profile-recovery" size="small" color="neutral-20"></lukso-icon>
<lukso-icon name="profile-recovery" size="small" color="neutral-20" class="pl-3 pr-3"></lukso-icon>
</lukso-button>
</lukso-tooltip>
</div>
Expand Down
24 changes: 16 additions & 8 deletions components/ProfileSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { useCallback, useState } from 'react';
import { request, gql } from 'graphql-request';
import makeBlockie from 'ethereum-blockies-base64';
import { useUpProvider } from './upProvider';
import Image from 'next/image';

const ENVIO_TESTNET_URL = 'https://envio.lukso-testnet.universal.tech/v1/graphql';
const ENVIO_MAINNET_URL = 'https://envio.lukso-mainnet.universal.tech/v1/graphql';

const gqlQuery = gql`
query MyQuery($id: String!) {
Expand Down Expand Up @@ -57,12 +61,12 @@ type SearchProps = {
};

export function ProfileSearch({ onSelectAddress }: SearchProps) {
const { setIsSearching } = useUpProvider();
const { chainId, setIsSearching } = useUpProvider();
const [query, setQuery] = useState('');
const [results, setResults] = useState<Profile[]>([]);
const [loading, setLoading] = useState(false);
const [showDropdown, setShowDropdown] = useState(false);

const handleSearch = useCallback(
async (searchQuery: string, forceSearch: boolean = false) => {
setQuery(searchQuery);
Expand All @@ -80,12 +84,12 @@ export function ProfileSearch({ onSelectAddress }: SearchProps) {

setLoading(true);
try {
const envioUrl = chainId === 42 ? ENVIO_MAINNET_URL : ENVIO_TESTNET_URL;
const { search_profiles: data } = (await request(
'https://envio.lukso-testnet.universal.tech/v1/graphql',
envioUrl,
gqlQuery,
{ id: searchQuery }
)) as { search_profiles: Profile[] };

setResults(data);
setShowDropdown(true);
} catch (error) {
Expand All @@ -95,7 +99,7 @@ export function ProfileSearch({ onSelectAddress }: SearchProps) {
setLoading(false);
}
},
[]
[chainId]
);

const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
Expand All @@ -120,10 +124,12 @@ export function ProfileSearch({ onSelectAddress }: SearchProps) {
const getProfileImage = (profile: Profile) => {
if (profile.profileImages && profile.profileImages.length > 0) {
return (
<img
<Image
src={profile.profileImages[0].src}
alt={`${profile.name || profile.id} avatar`}
className="mt-1 w-10 h-10 rounded-full flex-shrink-0 object-cover"
width={40}
height={40}
onError={(e) => {
// Fallback to blockie if image fails to load
e.currentTarget.src = makeBlockie(profile.id);
Expand All @@ -133,10 +139,12 @@ export function ProfileSearch({ onSelectAddress }: SearchProps) {
}

return (
<img
<Image
src={makeBlockie(profile.id)}
alt={`${profile.name || profile.id} avatar`}
className="w-10 h-10 rounded-full flex-shrink-0"
width={40}
height={40}
/>
);
};
Expand Down Expand Up @@ -210,7 +218,7 @@ export function ProfileSearch({ onSelectAddress }: SearchProps) {
address={result.id}
max-width="200"
size="large"
slice-by="8"
slice-by="4"
address-color=""
name-color=""
custom-class=""
Expand Down
Loading