Skip to content
Closed
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion apps/backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

node_modules
# OS
.DS_Store

Expand Down
20 changes: 20 additions & 0 deletions apps/backend/src/clients/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import api from './client';
import { setTokens } from '../utils/token';

export const login = async (email: string, password: string) => {
const response = await api.post('/auth/login', { email, password });

const { accessToken, refreshToken } = response.data;

Check failure on line 7 in apps/backend/src/clients/auth.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe assignment of an `any` value

Check failure on line 7 in apps/backend/src/clients/auth.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe assignment of an `any` value

setTokens(accessToken, refreshToken);

Check warning on line 9 in apps/backend/src/clients/auth.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `string`

Check warning on line 9 in apps/backend/src/clients/auth.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `string`

Check warning on line 9 in apps/backend/src/clients/auth.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `string`

Check warning on line 9 in apps/backend/src/clients/auth.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `string`

return response.data;

Check failure on line 11 in apps/backend/src/clients/auth.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe return of a value of type `any`

Check failure on line 11 in apps/backend/src/clients/auth.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe return of a value of type `any`
};

export const register = async (data: any) => {
return api.post('/auth/register', data);
};

export const logout = async () => {
await api.post('/auth/logout');
};
90 changes: 90 additions & 0 deletions apps/backend/src/clients/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import axios, { AxiosError } from 'axios';
import { getAccessToken, getRefreshToken, setTokens, } from '../utils/token';
import { clearTokens } from '../utils/token';

const api = axios.create({
baseURL: process.env.VITE_API_URL || 'http://localhost:3000',
withCredentials: true,
});



api.interceptors.request.use(

Check failure on line 12 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe assignment of an `any` value

Check failure on line 12 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe assignment of an `any` value
(config) => {
const token = getAccessToken();

if (token) {
config.headers.Authorization = `Bearer ${token}`;
}

return config;

Check failure on line 20 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Expected the Promise rejection reason to be an Error

Check failure on line 20 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Expected the Promise rejection reason to be an Error
},
(error) => Promise.reject(error),
);


// 🔄 RESPONSE INTERCEPTOR (Refresh Flow)
let isRefreshing = false;
let failedQueue: any[] = [];

Check failure on line 29 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe member access .reject on an `any` value

Check failure on line 29 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe call of an `any` typed value

Check failure on line 29 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe member access .reject on an `any` value

Check failure on line 29 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe call of an `any` typed value
const processQueue = (error: any, token: string | null = null) => {

Check failure on line 30 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe member access .resolve on an `any` value

Check failure on line 30 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe call of an `any` typed value

Check failure on line 30 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe member access .resolve on an `any` value

Check failure on line 30 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe call of an `any` typed value
failedQueue.forEach((prom) => {
if (error) prom.reject(error);
else prom.resolve(token);
});

failedQueue = [];
};

api.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {

Check failure on line 41 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe member access ._retry on an `any` value

Check failure on line 41 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe member access ._retry on an `any` value
const originalRequest: any = error.config;

if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise((resolve, reject) => {

Check failure on line 46 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe member access .headers on an `any` value

Check failure on line 46 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe member access .headers on an `any` value
failedQueue.push({ resolve, reject });

Check warning on line 47 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `AxiosRequestConfig<any>`

Check warning on line 47 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `AxiosRequestConfig<any>`
}).then((token) => {
originalRequest.headers['Authorization'] = 'Bearer ' + token;
return api(originalRequest);
});
}

originalRequest._retry = true;
isRefreshing = true;

try {
const refreshToken = getRefreshToken();

const response = await axios.post(
`${process.env.VITE_API_URL}/auth/refresh`,
{ refreshToken },
);

Check warning on line 64 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `string`

Check warning on line 64 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `string`

Check warning on line 64 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `string`

Check warning on line 64 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `string`
const { accessToken, refreshToken: newRefreshToken } = response.data;

Check warning on line 66 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `string | null`

Check warning on line 66 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `string | null`
setTokens(accessToken, newRefreshToken);

processQueue(null, accessToken);

Check warning on line 70 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `AxiosRequestConfig<any>`

Check warning on line 70 in apps/backend/src/clients/client.ts

View workflow job for this annotation

GitHub Actions / build

Unsafe argument of type `any` assigned to a parameter of type `AxiosRequestConfig<any>`
originalRequest.headers['Authorization'] = 'Bearer ' + accessToken;

return api(originalRequest);
} catch (err) {
processQueue(err, null);
clearTokens();
if (typeof globalThis.window !== 'undefined') {
globalThis.window.location.href = '/login';
}
return Promise.reject(err);
} finally {
isRefreshing = false;
}
}

return Promise.reject(error);
},
);

export default api;
21 changes: 21 additions & 0 deletions apps/backend/src/services/escrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import api from "src/clients/client";

export const createEscrow = async (payload: any) => {
const response = await api.post('/escrow', payload);
return response.data;
};

export const getEscrows = async () => {
const response = await api.get('/escrow');
return response.data;
};

export const getEscrowById = async (id: string) => {
const response = await api.get(`/escrow/${id}`);
return response.data;
};

export const releaseEscrow = async (id: string) => {
const response = await api.patch(`/escrow/${id}/release`);
return response.data;
};
30 changes: 30 additions & 0 deletions apps/backend/src/utils/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const ACCESS_KEY = 'accessToken';
const REFRESH_KEY = 'refreshToken';

export const setTokens = (access: string, refresh: string) => {
if (typeof globalThis.window !== 'undefined') {
globalThis.window.localStorage.setItem(ACCESS_KEY, access);
globalThis.window.localStorage.setItem(REFRESH_KEY, refresh);
}
};

export const getAccessToken = () => {
if (typeof globalThis.window !== 'undefined') {
return globalThis.window.localStorage.getItem(ACCESS_KEY);
}
return null;
};

export const getRefreshToken = () => {
if (typeof globalThis.window !== 'undefined') {
return globalThis.window.localStorage.getItem(REFRESH_KEY);
}
return null;
};

export const clearTokens = () => {
if (typeof globalThis.window !== 'undefined') {
globalThis.window.localStorage.removeItem(ACCESS_KEY);
globalThis.window.localStorage.removeItem(REFRESH_KEY);
}
};
9 changes: 9 additions & 0 deletions apps/frontend/app/escrow/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import TransactionHistory from '@/components/escrow/detail/TransactionHistory';
import ActivityFeed from '@/components/common/ActivityFeed';
import { IEscrowExtended } from '@/types/escrow';
import FileDisputeModal from '@/components/escrow/detail/file-dispute-modal';
import { Button } from '@/components/ui/button';

const EscrowDetailPage = () => {
const { id } = useParams();
Expand Down Expand Up @@ -56,6 +58,7 @@
</div>
);
}
const [disputeOpen, setDisputeOpen] = useState(false);

Check failure on line 61 in apps/frontend/app/escrow/[id]/page.tsx

View workflow job for this annotation

GitHub Actions / Build and Test

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

Check failure on line 61 in apps/frontend/app/escrow/[id]/page.tsx

View workflow job for this annotation

GitHub Actions / Build and Test

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

if (!escrow) {
return (
Expand Down Expand Up @@ -104,6 +107,12 @@
</div>
</div>
</div>

<FileDisputeModal
open={disputeOpen}
onClose={() => setDisputeOpen(false)}
escrowId={escrow.id}
/>
</div>
);
};
Expand Down
126 changes: 113 additions & 13 deletions apps/frontend/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,26 +1,126 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";

:root {
--background: #ffffff;
--foreground: #171717;
}
@custom-variant dark (&:is(.dark *));

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}

body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}

@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
2 changes: 2 additions & 0 deletions apps/frontend/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Providers from "@/component/Providers";
import Navbar from "@/component/layout/Navbar";
import FileDisputeModal from "@/components/escrow/detail/file-dispute-modal";

const geistSans = Geist({
variable: "--font-geist-sans",
Expand Down Expand Up @@ -31,6 +32,7 @@ export default function RootLayout({
>
<Providers>
<Navbar />

<main className="pt-16">
{children}
</main>
Expand Down
7 changes: 5 additions & 2 deletions apps/frontend/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Image from "next/image";

import Image from "next/image";
import Link from "next/link";

export default function Home() {
Expand Down Expand Up @@ -42,7 +43,8 @@ export default function Home() {
</Link>
</div>
</div>



<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-12 max-w-4xl">
<div className="bg-white p-6 rounded-xl shadow-md border border-gray-100">
<div className="text-blue-600 text-3xl mb-4">🔒</div>
Expand All @@ -60,6 +62,7 @@ export default function Home() {
<div className="text-teal-600 text-3xl mb-4">🌐</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">Global Access</h3>
<p className="text-gray-600">Access your escrow agreements from anywhere in the world</p>

</div>
</div>
</main>
Expand Down
Loading
Loading