fix: 提交短链服务、sentry监控
This commit is contained in:
parent
3c615237e7
commit
57173a0221
@ -5,12 +5,9 @@ import useGlobalStore from '@/config/use-global';
|
||||
import { Card, CardContent } from '@workspace/airo-ui/components/card';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
import CopyShortenedLink from '@/components/CopyShortenedLink/CopyShortenedLink';
|
||||
import Recharge from '@/components/subscribe/recharge';
|
||||
import SvgIcon from '@/components/SvgIcon';
|
||||
import { Button } from '@workspace/airo-ui/components/button';
|
||||
import Link from 'next/link';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { toast } from 'sonner';
|
||||
import Table from './components/Table/Table';
|
||||
import WalletDialog from './components/WalletDialog/WalletDialog';
|
||||
|
||||
@ -67,18 +64,7 @@ export default function Page() {
|
||||
</p>
|
||||
<p className='flex justify-between text-base font-medium text-[#225BA9]'>
|
||||
<span> {user?.refer_code}</span>
|
||||
<CopyToClipboard
|
||||
text={`${location?.origin}/?invite=${user?.refer_code}`}
|
||||
onCopy={(text, result) => {
|
||||
if (result) {
|
||||
toast.success(dashboardT('copySuccess'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button variant='link' size='sm' className='h-auto p-0 px-0 [&_svg]:size-5'>
|
||||
<SvgIcon name={'copy'}></SvgIcon>
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
<CopyShortenedLink className={'sm:block'} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,12 +1,3 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
export default async function MainLayout({ children }: { children: React.ReactNode }) {
|
||||
const CrispWithNoSSR = dynamic(() => import('@/components/Crisp/Crisp'));
|
||||
|
||||
return (
|
||||
<>
|
||||
<CrispWithNoSSR />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
47
apps/user/app/api/kutt/route.ts
Normal file
47
apps/user/app/api/kutt/route.ts
Normal file
@ -0,0 +1,47 @@
|
||||
// 你的后端 API 密钥,请确保将其存储在 .env.local 文件中
|
||||
const BACKEND_API_KEY = 'Q4PuYh7J2H_DlW2X4XUrwYV-yaKty8dw0dwP4LXM';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
// 从客户端请求中获取 JSON 数据
|
||||
const requestBody = await request.json();
|
||||
// 检查请求的 hostname 是否为本地地址 (localhost 或 127.0.0.1)
|
||||
if (requestBody?.target?.includes('localhost') || requestBody?.target?.includes('127.0.0.1')) {
|
||||
console.log('Request is from localhost, returning self.');
|
||||
// 如果是本地请求,直接返回一个响应,不做转发
|
||||
return NextResponse.json({
|
||||
link: requestBody.target,
|
||||
});
|
||||
}
|
||||
|
||||
// 将客户端请求的 JSON 数据转发到真正的后端 API
|
||||
const backendResponse = await fetch('https://url.airoport.co/api/v2/links', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// 🚨 在这里使用你的后端 API 密钥,它只在服务器端运行,非常安全
|
||||
'x-api-key': BACKEND_API_KEY,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
reuse: true,
|
||||
...requestBody,
|
||||
}),
|
||||
});
|
||||
|
||||
// 检查后端响应状态
|
||||
if (!backendResponse.ok) {
|
||||
// 转发后端 API 的错误响应
|
||||
const errorData = await backendResponse.json();
|
||||
return Response.json(errorData, { status: backendResponse.status });
|
||||
}
|
||||
|
||||
const backendData = await backendResponse.json();
|
||||
|
||||
// 将后端 API 的响应转发给前端
|
||||
return Response.json(backendData);
|
||||
} catch (error) {
|
||||
console.error('Proxy API Error:', error);
|
||||
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ import { useCountDown } from 'ahooks';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface SendCodeProps {
|
||||
type: 'email' | 'phone';
|
||||
@ -47,6 +48,7 @@ export default function SendCode({ type, params, form }: SendCodeProps) {
|
||||
const setCodeTimer = () => {
|
||||
const endTime = Date.now() + verify_code_interval * 1000;
|
||||
setTargetDate(endTime);
|
||||
toast.success(t('register.sendCode'));
|
||||
localStorage.setItem(`verify_code_${type}`, endTime.toString());
|
||||
};
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import { unstable_noStore as noStore } from 'next/cache';
|
||||
import { Inter } from 'next/font/google';
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
// import { Geist, Geist_Mono } from 'next/font/google';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Metadata, Viewport } from 'next/types';
|
||||
import NextTopLoader from 'nextjs-toploader';
|
||||
@ -99,6 +100,7 @@ export default async function RootLayout({
|
||||
console.log('Error fetching user info:', error);
|
||||
}
|
||||
}
|
||||
const CrispWithNoSSR = dynamic(() => import('@/components/Crisp/Crisp'));
|
||||
|
||||
return (
|
||||
<html suppressHydrationWarning lang={locale} dir={getLangDir(locale)}>
|
||||
@ -120,6 +122,7 @@ export default async function RootLayout({
|
||||
<NextTopLoader showSpinner={false} />
|
||||
<Providers common={{ ...config }} user={user}>
|
||||
<Toaster richColors closeButton />
|
||||
<CrispWithNoSSR />
|
||||
{children}
|
||||
</Providers>
|
||||
</NextIntlClientProvider>
|
||||
|
||||
66
apps/user/components/CopyShortenedLink/CopyShortenedLink.tsx
Normal file
66
apps/user/components/CopyShortenedLink/CopyShortenedLink.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
// components/CopyShortenedLink.jsx
|
||||
import SvgIcon from '@/components/SvgIcon';
|
||||
import useGlobalStore from '@/config/use-global';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Button } from '@workspace/airo-ui/components/button';
|
||||
import { cn } from '@workspace/airo-ui/lib/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const CopyShortenedLink = ({ className }: { className?: string }) => {
|
||||
const t = useTranslations('affiliate');
|
||||
const { user } = useGlobalStore();
|
||||
|
||||
// 构建长链接,使用用户的唯一标识符作为查询键
|
||||
const target = `${location?.origin}/?invite=${user?.refer_code}`;
|
||||
const queryKey = ['short-url', user?.refer_code];
|
||||
|
||||
const { data: shortUrl } = useQuery({
|
||||
queryKey: queryKey,
|
||||
queryFn: async () => {
|
||||
const response = await fetch('/api/kutt', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ target }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
|
||||
// 关键步骤:解析 JSON 数据并返回
|
||||
const json = await response.json();
|
||||
console.log('CopyShortened link', json);
|
||||
return json.link ?? null;
|
||||
},
|
||||
enabled: !!user?.refer_code, // 默认不自动执行
|
||||
staleTime: Infinity, // 数据永不过期,除非手动失效
|
||||
});
|
||||
// 渲染组件
|
||||
return (
|
||||
<CopyToClipboard
|
||||
text={shortUrl} // 如果 shortUrl 还没有值,则传递空字符串
|
||||
onCopy={(text, result) => {
|
||||
if (text) {
|
||||
toast.success('text is undefined');
|
||||
return '';
|
||||
}
|
||||
if (result) {
|
||||
toast.success(t('copySuccess'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* 按钮的逻辑 */}
|
||||
<Button variant='link' size='sm' className={cn('h-auto p-0 px-0 [&_svg]:size-5', className)}>
|
||||
<SvgIcon name={'copy'}></SvgIcon>
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyShortenedLink;
|
||||
@ -4,8 +4,8 @@ import {
|
||||
AffiliateDialog,
|
||||
AffiliateDialogRef,
|
||||
} from '@/components/affiliate/components/AffiliateDialog';
|
||||
import CopyShortenedLink from '@/components/CopyShortenedLink/CopyShortenedLink';
|
||||
import { Display } from '@/components/display';
|
||||
import SvgIcon from '@/components/SvgIcon';
|
||||
import useGlobalStore from '@/config/use-global';
|
||||
import { querySubscribeList } from '@/services/user/subscribe';
|
||||
import { queryUserAffiliate, queryUserAffiliateList } from '@/services/user/user';
|
||||
@ -17,8 +17,6 @@ import { default as Airo_Empty } from '@workspace/airo-ui/custom-components/empt
|
||||
import { formatDate } from '@workspace/airo-ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useRef, useState } from 'react';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function Affiliate() {
|
||||
const t = useTranslations('affiliate');
|
||||
@ -44,11 +42,11 @@ export default function Affiliate() {
|
||||
});
|
||||
|
||||
const { data: proPlan } = useQuery({
|
||||
queryKey: ['querySubscribeList1'],
|
||||
queryKey: ['querySubscribeList'],
|
||||
queryFn: async () => {
|
||||
const { data } = await querySubscribeList();
|
||||
const list = data.data?.list?.filter((v) => v.unit_time === 'Month') || [];
|
||||
return list.find((v) => v.name.includes('Pro'));
|
||||
return list.find((v) => v.name.includes('Pro')) ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
@ -83,41 +81,11 @@ export default function Affiliate() {
|
||||
<div className='rounded-[20px] border-2 border-[#D9D9D9] px-4 py-2 shadow-sm transition-all duration-300 hover:shadow-md sm:px-5 sm:pb-2 sm:pt-4'>
|
||||
<p className='flex justify-between font-light text-[#666] sm:mb-3 sm:text-sm'>
|
||||
{t('commissionInviteCode')}
|
||||
<CopyToClipboard
|
||||
text={`${location?.origin}/?invite=${user?.refer_code}`}
|
||||
onCopy={(text, result) => {
|
||||
if (result) {
|
||||
toast.success(t('copySuccess'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant='link'
|
||||
size='sm'
|
||||
className='h-auto p-0 px-0 sm:hidden [&_svg]:size-5'
|
||||
>
|
||||
<SvgIcon name={'copy'}></SvgIcon>
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
<CopyShortenedLink className={'sm:hidden'} />
|
||||
</p>
|
||||
<p className='flex justify-between text-xl font-bold text-[#225BA9]'>
|
||||
<span> {user?.refer_code}</span>
|
||||
<CopyToClipboard
|
||||
text={`${location?.origin}/?invite=${user?.refer_code}`}
|
||||
onCopy={(text, result) => {
|
||||
if (result) {
|
||||
toast.success(t('copySuccess'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant='link'
|
||||
size='sm'
|
||||
className='hidden gap-2 p-0 sm:block [&_svg]:size-5'
|
||||
>
|
||||
<SvgIcon name={'copy'}></SvgIcon>
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
<CopyShortenedLink className={'sm:block'} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import OfferDialog from './OfferDialog/index';
|
||||
// import OfferDialog from './OfferDialog/index';
|
||||
|
||||
export default function HomeContent() {
|
||||
const t = useTranslations('components.home');
|
||||
@ -17,7 +17,7 @@ export default function HomeContent() {
|
||||
{t('anywhere')}
|
||||
</h1>
|
||||
{/* 副标题 */}
|
||||
<div className='mb-12 text-left text-[17px] leading-normal text-white sm:mb-16 sm:text-center sm:font-bold'>
|
||||
<div className='text-left text-[17px] leading-normal text-white sm:mb-16 sm:text-center sm:font-bold'>
|
||||
<p className={'w-[255px] sm:w-full'}>
|
||||
<span className='mr-2 text-white'>
|
||||
Airo<sup className='text-[8px]'>™</sup>Port
|
||||
@ -27,7 +27,7 @@ export default function HomeContent() {
|
||||
<p className={'mt-1 w-[255px] sm:mt-0 sm:w-full'}>{t('getSubscription')}</p>
|
||||
</div>
|
||||
{/* 按钮 */}
|
||||
<OfferDialog />
|
||||
{/*<OfferDialog />*/}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://e519096f8b71cba99d86ddc46d8e424f@o4509950153719808.ingest.us.sentry.io/4509950194679808',
|
||||
@ -20,6 +21,8 @@ Sentry.init({
|
||||
// in development and sample at a lower rate in production
|
||||
replaysSessionSampleRate: 0.1,
|
||||
|
||||
enabled: isProduction,
|
||||
|
||||
// Define how likely Replay events are sampled when an error occurs.
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
"description": "Create a new account, fill in your information to register.",
|
||||
"email": "Please enter a valid email address.",
|
||||
"existingAccount": "Already have an account?",
|
||||
"sendCode": "The verification code was sent successfully. If you did not receive it, please check your spam folder.",
|
||||
"get": "Get",
|
||||
"invite": "Invitation code",
|
||||
"message": "#### Dear User, Hello!\n\nThank you for your attention and support. Due to adjustments in site operation strategy, we have closed the new user registration function. During this period, existing users will not be affected.\n\nWe are committed to providing you with better service and experience, so we will conduct comprehensive system optimization and feature upgrades during the registration closure. In the future, we will welcome you with better content and services.\n\nPlease follow our website and social media platforms to get the latest updates and notifications. Thank you for your understanding and support.\n\nIf you have any questions or need assistance, please feel free to contact our customer service team.\n\n**Thank you again for your support and understanding.**",
|
||||
|
||||
@ -35,7 +35,8 @@
|
||||
"success": "注册成功,已自动登录!",
|
||||
"switchToLogin": "登录",
|
||||
"title": "注册",
|
||||
"whitelist": "电子邮件域名不在允许的白名单中。"
|
||||
"whitelist": "电子邮件域名不在允许的白名单中。",
|
||||
"sendCode": "验证码发送成功,如未收到请检查垃圾邮件"
|
||||
},
|
||||
"reset": {
|
||||
"description": "请输入您的电子邮件地址以重置密码。",
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://e519096f8b71cba99d86ddc46d8e424f@o4509950153719808.ingest.us.sentry.io/4509950194679808',
|
||||
@ -14,6 +15,8 @@ Sentry.init({
|
||||
// Enable logs to be sent to Sentry
|
||||
enableLogs: true,
|
||||
|
||||
enabled: isProduction,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
});
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://e519096f8b71cba99d86ddc46d8e424f@o4509950153719808.ingest.us.sentry.io/4509950194679808',
|
||||
|
||||
@ -13,6 +15,8 @@ Sentry.init({
|
||||
// Enable logs to be sent to Sentry
|
||||
enableLogs: true,
|
||||
|
||||
enabled: isProduction,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user