Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0ae10cc
feat: Update warning styles and add recipient accuracy reminder in forms
Dprof-in-tech Oct 3, 2025
e8bfba4
Merge branch 'main' into fix-add-reciepient-alert-manual-name-verific…
Dprof-in-tech Nov 5, 2025
6a98e9b
Merge branch 'main' into fix-add-reciepient-alert-manual-name-verific…
Dprof-in-tech Nov 5, 2025
3cda2d4
feat(recipients): enhance recipient name verification and editing fun…
Dprof-in-tech Nov 6, 2025
203188b
fix(TransactionForm): correct conditional rendering for recipient ver…
Dprof-in-tech Nov 6, 2025
44ee74e
fix(RecipientDetailsForm): update feedback message display based on r…
Dprof-in-tech Nov 6, 2025
7050164
fix(RecipientDetailsForm): improve feedback message display logic for…
Dprof-in-tech Nov 6, 2025
d64cfc0
fix(RecipientDetailsForm): refine feedback message display logic for …
Dprof-in-tech Nov 6, 2025
c191119
fix(RecipientDetailsForm): update feedback message display logic for …
Dprof-in-tech Nov 6, 2025
4f7330a
fix(RecipientDetailsForm): implement alert component for recipient ve…
Dprof-in-tech Nov 6, 2025
22dc0a6
fix(RecipientDetailsForm): update styling of 'Learn more' link in ale…
Dprof-in-tech Nov 6, 2025
919e34e
fix(RecipientDetailsForm): refine alert visibility logic based on rec…
Dprof-in-tech Nov 6, 2025
ea01a92
fix(RecipientDetailsForm): include form validation errors in alert vi…
Dprof-in-tech Nov 6, 2025
06dafbf
feat: complete glitchtip integration with sentry wizard (#297)
sundayonah Dec 17, 2025
4553987
feat: enhance user tracking with server-side event logging (#302)
sundayonah Dec 17, 2025
0c61121
refactor: update next.config.mjs for improved configuration management
chibie Dec 17, 2025
a628ef1
feat: add low-memory build support and disable sourcemaps in next.con…
chibie Dec 18, 2025
e6036a3
Fixed a dependncy issue with the Privy-react-auth dependency .
Dprof-in-tech Dec 18, 2025
eae5321
change img to Image tag
Dprof-in-tech Dec 18, 2025
7fe4350
Merge branch 'main' into fix-add-reciepient-alert-manual-name-verific…
Dprof-in-tech Dec 19, 2025
f87d107
add blog link to learn more for accoutnname reconcillation
Dprof-in-tech Dec 19, 2025
2d66620
Merge branch 'main' into fix-add-reciepient-alert-manual-name-verific…
Dprof-in-tech Feb 3, 2026
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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,8 @@ SANITY_STUDIO_PROJECT_ID=your_project_id_here
# Next.js App (client-side)
NEXT_PUBLIC_SANITY_DATASET=production
NEXT_PUBLIC_SANITY_PROJECT_ID=your_project_id_here

# Sentry
SENTRY_DSN=https://abc123@o456.ingest.glitchtip.pycrst.xyz/789
SENTRY_URL=
SENTRY_AUTH_TOKEN=
46 changes: 46 additions & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Node.js CI

on:
push:
branches: ['stable']
pull_request:
branches: ['main', 'stable']

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Install pnpm
run: npm install -g pnpm

- name: Verify pnpm installation
run: pnpm --version

- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm build
7 changes: 6 additions & 1 deletion app/api/track-logout/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import { trackServerEvent } from "@/app/lib/server-analytics";
import { trackServerEvent, identifyServerUser } from "@/app/lib/server-analytics";

export async function POST(request: NextRequest) {
try {
Expand Down Expand Up @@ -52,6 +52,11 @@ export async function POST(request: NextRequest) {
wallet,
);

// Update last_seen timestamp on user identification (additive enhancement)
identifyServerUser(wallet, {
$last_seen: new Date().toISOString(),
});

return NextResponse.json({
success: true,
message: "Logout event tracked successfully",
Expand Down
203 changes: 203 additions & 0 deletions app/api/v1/analytics/identify/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { NextRequest, NextResponse } from "next/server";
import { withRateLimit } from "@/app/lib/rate-limit";
import {
identifyServerUser,
trackApiRequest,
trackApiResponse,
trackApiError,
} from "@/app/lib/server-analytics";

/**
* Server-side user identification API endpoint
* Accepts user data from client and identifies user in Mixpanel server-side
* Falls back to body walletAddress if not available in auth context
*/
export const POST = withRateLimit(async (request: NextRequest) => {
const startTime = Date.now();

try {
const body = await request.json().catch(() => null);
if (!body || typeof body !== "object") {
// Don't track errors for invalid JSON bodies - likely bots or malformed requests
// Just return 400 without logging to Mixpanel to reduce noise
return NextResponse.json(
{ success: false, error: "Invalid JSON body" },
{ status: 400 },
);
}

const {
walletAddress: bodyWalletAddress,
properties = {},
} = body as {
walletAddress?: string;
properties?: {
login_method?: string | null;
isNewUser?: boolean;
createdAt?: Date | string;
email?: { address: string } | null;
};
};

// Try to get wallet address from auth context (middleware header) first
// Fall back to body walletAddress if not available
const walletAddress =
request.headers.get("x-wallet-address")?.toLowerCase() ||
(typeof bodyWalletAddress === "string"
? bodyWalletAddress.toLowerCase()
: null);

if (!walletAddress) {
const response = NextResponse.json(
{ success: false, error: "Missing wallet address" },
{ status: 400 },
);
// Track error asynchronously
Promise.resolve().then(() => {
try {
trackApiError(
request,
"/api/v1/analytics/identify",
"POST",
new Error("Missing wallet address"),
400,
);
} catch (e) { }
}).catch(() => { });
return response;
}

// Validate wallet address format
if (!/^0x[a-fA-F0-9]{40}$/.test(walletAddress)) {
const response = NextResponse.json(
{ success: false, error: "Invalid wallet address format" },
{ status: 400 },
);
// Track error asynchronously
Promise.resolve().then(() => {
try {
trackApiError(
request,
"/api/v1/analytics/identify",
"POST",
new Error("Invalid wallet address format"),
400,
);
} catch (e) { }
}).catch(() => { });
return response;
}

// Prepare user properties for Mixpanel
const userProperties: {
login_method?: string;
isNewUser?: boolean;
$signup_date?: string;
$email?: string;
$last_login?: string;
} = {};

if (properties.login_method) {
userProperties.login_method = properties.login_method;
}

if (properties.isNewUser !== undefined) {
userProperties.isNewUser = properties.isNewUser;
}

if (properties.createdAt) {
userProperties.$signup_date =
properties.createdAt instanceof Date
? properties.createdAt.toISOString()
: properties.createdAt;
}

if (
process.env.NEXT_PUBLIC_ENABLE_EMAIL_IN_ANALYTICS === "true" &&
properties.email?.address
) {
userProperties.$email = properties.email.address;
}

// Set last login timestamp
userProperties.$last_login = new Date().toISOString();

// Return response immediately, then track asynchronously (non-blocking)
const responseTime = Date.now() - startTime;
const response = NextResponse.json({
success: true,
message: "User identified successfully",
timestamp: new Date().toISOString(),
});

// Execute tracking asynchronously after response (fire-and-forget)
Promise.resolve().then(() => {
try {
// Extract IP and User-Agent for geo-location and OS/Browser detection
const ip =
request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
request.headers.get("x-real-ip") ||
undefined;
const userAgent = request.headers.get("user-agent") || undefined;

// Track API request
const requestProperties: Record<string, any> = {};
if (ip) requestProperties.ip_address = ip;
if (userAgent) requestProperties.user_agent = userAgent;
trackApiRequest(request, "/api/v1/analytics/identify", "POST", requestProperties);

// Identify user server-side (IP and User-Agent passed for geo-location/device info)
const identifyProperties: Record<string, any> = { ...userProperties };
if (ip) identifyProperties.ip_address = ip;
if (userAgent) identifyProperties.user_agent = userAgent;
identifyServerUser(walletAddress, identifyProperties);

// Track successful API response
const responseProperties: Record<string, any> = {
wallet_address: walletAddress,
is_new_user: properties.isNewUser,
};
if (ip) responseProperties.ip_address = ip;
if (userAgent) responseProperties.user_agent = userAgent;
trackApiResponse("/api/v1/analytics/identify", "POST", 200, responseTime, responseProperties);
} catch (e) {
// Silently fail - tracking is fire-and-forget and should not break user flow
}
}).catch(() => {
// Silently ignore any Promise rejection
});

return response;
} catch (error) {
// Return error response immediately
const responseTime = Date.now() - startTime;
const response = NextResponse.json(
{
success: false,
error: "Failed to identify user",
},
{ status: 500 },
);

// Track API error asynchronously (non-blocking)
Promise.resolve().then(() => {
try {
trackApiError(
request,
"/api/v1/analytics/identify",
"POST",
error as Error,
500,
{
response_time_ms: responseTime,
},
);
} catch (e) {
// Ignore tracking errors
}
}).catch(() => { });

return response;
}
});

Loading
Loading