feat: 10.10线上最新代码提交

This commit is contained in:
zdl
2025-10-11 16:16:02 +08:00
parent 4d0dc109bc
commit c1132cd0d6
2750 changed files with 11314 additions and 152745 deletions

View File

@@ -0,0 +1,40 @@
type ArrowsProps = {
className?: string;
prevClassName?: string;
nextClassName?: string;
onPrev?: () => void;
onNext?: () => void;
};
const Arrows = ({
className,
prevClassName,
nextClassName,
onPrev,
onNext,
}: ArrowsProps) => (
<div className={`splide__arrows relative z-10 flex ${className || ""}`}>
<button
className={`splide__arrow splide__arrow--prev ${
prevClassName || ""
}`}
onClick={onPrev}
>
<svg className="w-4 h-4 fill-n-4">
<path d="M8.707 1.707a1 1 0 0 0 0-1.414 1 1 0 0 0-1.414 0l-7 7a1 1 0 0 0 0 1.414l7 7a1 1 0 0 0 1.414-1.414L3.414 9H15a1 1 0 1 0 0-2H3.414l5.293-5.293z" />
</svg>
</button>
<button
className={`splide__arrow splide__arrow--next ${
nextClassName || ""
}`}
onClick={onNext}
>
<svg className="w-4 h-4 fill-n-4">
<path d="M7.293 1.707a1 1 0 0 1 0-1.414 1 1 0 0 1 1.414 0l7 7a1 1 0 0 1 0 1.414l-7 7a1 1 0 0 1-1.414-1.414L12.586 9H1a1 1 0 1 1 0-2h11.586L7.293 1.707z" />
</svg>
</button>
</div>
);
export default Arrows;

View File

@@ -0,0 +1,40 @@
import { Link } from "react-router-dom";
import { svgs } from "./svgs";
const Button = ({
className,
href,
onClick,
children,
px,
white,
}) => {
const classes = `button relative inline-flex items-center justify-center h-11 ${
px || "px-7"
} ${white ? "text-n-8" : "text-n-1"} transition-colors hover:text-color-1 ${
className || ""
}`;
const spanClasses = `relative z-10`;
return href ? (
href.startsWith("mailto:") ? (
<a href={href} className={classes}>
<span className={spanClasses}>{children}</span>
{svgs(white)}
</a>
) : (
<Link href={href} className={classes}>
<span className={spanClasses}>{children}</span>
{svgs(white)}
</Link>
)
) : (
<button className={classes} onClick={onClick}>
<span className={spanClasses}>{children}</span>
{svgs(white)}
</button>
);
};
export default Button;

View File

@@ -0,0 +1,58 @@
export const svgs = (white: boolean | undefined) => (
<>
<svg
className="absolute top-0 left-0"
width="21"
height="44"
viewBox="0 0 21 44"
>
<path
fill={white ? "white" : "none"}
stroke={white ? "white" : "url(#btn-left)"}
strokeWidth="2"
d="M21,43.00005 L8.11111,43.00005 C4.18375,43.00005 1,39.58105 1,35.36365 L1,8.63637 C1,4.41892 4.18375,1 8.11111,1 L21,1"
/>
</svg>
<svg
className="absolute top-0 left-[1.3125rem] w-[calc(100%-2.625rem)]"
height="44"
viewBox="0 0 100 44"
preserveAspectRatio="none"
fill={white ? "white" : "none"}
>
{white ? (
<polygon
fill="white"
fillRule="nonzero"
points="100 0 100 44 0 44 0 0"
/>
) : (
<>
<polygon
fill="url(#btn-top)"
fillRule="nonzero"
points="100 42 100 44 0 44 0 42"
/>
<polygon
fill="url(#btn-bottom)"
fillRule="nonzero"
points="100 0 100 2 0 2 0 0"
/>
</>
)}
</svg>
<svg
className="absolute top-0 right-0"
width="21"
height="44"
viewBox="0 0 21 44"
>
<path
fill={white ? "white" : "none"}
stroke={white ? "white" : "url(#btn-right)"}
strokeWidth="2"
d="M0,43.00005 L5.028,43.00005 L12.24,43.00005 C16.526,43.00005 20,39.58105 20,35.36365 L20,16.85855 C20,14.59295 18.978,12.44425 17.209,10.99335 L7.187,2.77111 C5.792,1.62675 4.034,1 2.217,1 L0,1"
/>
</svg>
</>
);

0
src/components/Calendars/EventCalendar.js Normal file → Executable file
View File

0
src/components/Card/Card.js Normal file → Executable file
View File

0
src/components/Card/CardBody.js Normal file → Executable file
View File

0
src/components/Card/CardHeader.js Normal file → Executable file
View File

0
src/components/Charts/BarChart.js Normal file → Executable file
View File

0
src/components/Charts/BubbleChart.js Normal file → Executable file
View File

0
src/components/Charts/DonutChart.js Normal file → Executable file
View File

0
src/components/Charts/LineBarChart.js Normal file → Executable file
View File

0
src/components/Charts/LineChart.js Normal file → Executable file
View File

0
src/components/Charts/PieChart.js Normal file → Executable file
View File

0
src/components/Charts/PolarChart.js Normal file → Executable file
View File

0
src/components/Charts/RadarChart.js Normal file → Executable file
View File

0
src/components/Configurator/Configurator.js Normal file → Executable file
View File

0
src/components/Editor/Editor.js Normal file → Executable file
View File

0
src/components/ErrorBoundary.js Normal file → Executable file
View File

0
src/components/FixedPlugin/FixedPlugin.js Normal file → Executable file
View File

0
src/components/Footer/Footer.js Normal file → Executable file
View File

View File

@@ -30,21 +30,10 @@ const Footer = () => (
</a>
</nav>
</div>
<div className="flex flex-col items-center space-y-2">
<div className="flex justify-center items-center">
<p className="caption text-n-4 lg:block">
© 2024 价值前沿. 保留所有权利.
</p>
<div className="flex flex-col sm:flex-row items-center space-y-1 sm:space-y-0 sm:space-x-4 text-xs text-n-4">
<a
href="https://beian.mps.gov.cn/#/query/webSearch?code=11010802046286"
rel="noreferrer"
target="_blank"
className="hover:text-n-1 transition-colors"
>
京公网安备11010802046286号
</a>
<span className="text-n-4">京ICP备2025107343号-1</span>
</div>
</div>
</Section>
);

View File

@@ -0,0 +1,20 @@
import Image from "../Image";
const Generating = ({ className }) => (
<div
className={`flex items-center h-[3.375rem] px-6 bg-n-8/80 rounded-[1.6875rem] ${
className || ""
} text-base`}
>
<Image
className="w-5 h-5 mr-4"
src="/images/loading.png"
width={20}
height={20}
alt="Loading"
/>
AI is generating|
</div>
);
export default Generating;

0
src/components/Icons/IconBox.js Normal file → Executable file
View File

54
src/components/Icons/Icons.js Normal file → Executable file
View File

@@ -680,3 +680,57 @@ export const ArgonLogoMinifiedLight = createIcon({
</svg>
),
});
export const WechatPayIcon = createIcon({
displayName: 'WechatPayIcon',
viewBox: '0 0 24 24',
path: (
<g>
<path
d='M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 01.213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 00.167-.054l1.903-1.114a.864.864 0 01.717-.098 10.16 10.16 0 002.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-5.523 2.619-7.21C11.923 8.507 11.695 8.5 11.465 8.5c-1.66 0-3.274.374-4.774 1.061-.276-.651-.43-1.353-.43-2.081C6.261 4.824 7.321 2.188 8.691 2.188zM18.639 11.698c-4.118 0-7.45 2.649-7.45 5.918 0 3.27 3.332 5.918 7.45 5.918.858 0 1.676-.109 2.435-.304.276-.097.574-.074.858.098l1.549.904c.065.035.13.054.167.054.163 0 .276-.132.276-.295a.516.516 0 00-.035-.195l-.325-1.235a.59.59 0 01.177-.665c1.543-1.235 2.524-2.982 2.524-4.98 0-3.269-3.332-5.918-7.45-5.918h-.176z'
fill='#00D924'
/>
<circle cx='5.5' cy='7.5' r='1' fill='white' />
<circle cx='8.5' cy='7.5' r='1' fill='white' />
<circle cx='11.5' cy='7.5' r='1' fill='white' />
<circle cx='15.5' cy='15.5' r='0.8' fill='white' />
<circle cx='18' cy='15.5' r='0.8' fill='white' />
<circle cx='20.5' cy='15.5' r='0.8' fill='white' />
</g>
),
});
export const QRCodeIcon = createIcon({
displayName: 'QRCodeIcon',
viewBox: '0 0 24 24',
path: (
<g fill='currentColor'>
<rect x='1' y='1' width='6' height='6' rx='1' stroke='currentColor' strokeWidth='1.5' fill='none' />
<rect x='17' y='1' width='6' height='6' rx='1' stroke='currentColor' strokeWidth='1.5' fill='none' />
<rect x='1' y='17' width='6' height='6' rx='1' stroke='currentColor' strokeWidth='1.5' fill='none' />
<rect x='3' y='3' width='2' height='2' />
<rect x='19' y='3' width='2' height='2' />
<rect x='3' y='19' width='2' height='2' />
<rect x='9' y='1' width='2' height='2' />
<rect x='13' y='1' width='2' height='2' />
<rect x='9' y='5' width='2' height='2' />
<rect x='13' y='5' width='2' height='2' />
<rect x='1' y='9' width='2' height='2' />
<rect x='5' y='9' width='2' height='2' />
<rect x='1' y='13' width='2' height='2' />
<rect x='5' y='13' width='2' height='2' />
<rect x='9' y='9' width='6' height='6' rx='1' stroke='currentColor' strokeWidth='1.5' fill='none' />
<rect x='11' y='11' width='2' height='2' />
<rect x='17' y='9' width='2' height='2' />
<rect x='21' y='9' width='2' height='2' />
<rect x='17' y='13' width='2' height='2' />
<rect x='21' y='13' width='2' height='2' />
<rect x='9' y='17' width='2' height='2' />
<rect x='13' y='17' width='2' height='2' />
<rect x='9' y='21' width='2' height='2' />
<rect x='13' y='21' width='2' height='2' />
<rect x='17' y='17' width='6' height='2' />
<rect x='17' y='21' width='6' height='2' />
</g>
),
});

View File

@@ -0,0 +1,17 @@
import { useState } from "react";
const Image = ({ className, ...props }) => {
const [loaded, setLoaded] = useState(false);
return (
<img
className={`inline-block align-top opacity-0 transition-opacity ${
loaded && "opacity-100"
} ${className}`}
onLoad={() => setLoaded(true)}
{...props}
/>
);
};
export default Image;

View File

@@ -0,0 +1,73 @@
import Section from "@/components/Section";
import Image from "@/components/Image";
import Button from "@/components/Button";
type JoinProps = {};
const Join = ({}: JoinProps) => (
<Section crosses>
<div className="container">
<div className="relative max-w-[43.125rem] mx-auto py-8 md:py-14 xl:py-0">
<div className="relative z-1 text-center">
<h1 className="h1 mb-6">
Be part of the future of{" "}
<span className="inline-block relative">
Brainwave
<Image
className="absolute top-full left-0 w-full"
src="/images/curve.png"
width={624}
height={28}
alt="Curve"
/>
</span>
</h1>
<p className="body-1 mb-8 text-n-4">
Unleash the power of AI within Brainwave. Upgrade your
productivity with Brainwave, the open AI chat app.
</p>
<Button href="/pricing" white>
Get started
</Button>
</div>
<div className="absolute top-1/2 left-1/2 w-[46.5rem] h-[46.5rem] border border-n-2/5 rounded-full -translate-x-1/2 -translate-y-1/2 pointer-events-none">
<div className="absolute top-1/2 left-1/2 w-[39.25rem] h-[39.25rem] border border-n-2/10 rounded-full -translate-x-1/2 -translate-y-1/2"></div>
<div className="absolute top-1/2 left-1/2 w-[30.625rem] h-[30.625rem] border border-n-2/10 rounded-full -translate-x-1/2 -translate-y-1/2"></div>
<div className="absolute top-1/2 left-1/2 w-[21.5rem] h-[21.5rem] border border-n-2/10 rounded-full -translate-x-1/2 -translate-y-1/2"></div>
<div className="absolute top-1/2 left-1/2 w-[13.75rem] h-[13.75rem] border border-n-2/10 rounded-full -translate-x-1/2 -translate-y-1/2"></div>
</div>
<div className="absolute top-1/2 left-1/2 w-[46.5rem] h-[46.5rem] border border-n-2/5 rounded-full -translate-x-1/2 -translate-y-1/2 opacity-60 mix-blend-color-dodge pointer-events-none">
<div className="absolute top-1/2 left-1/2 w-[58.85rem] h-[58.85rem] -translate-x-3/4 -translate-y-1/2">
<Image
className="w-full"
src="/images/gradient.png"
width={942}
height={942}
alt="Gradient"
/>
</div>
</div>
</div>
</div>
<div className="absolute -top-[5.75rem] left-[18.5rem] -z-1 w-[19.8125rem] pointer-events-none lg:-top-15 lg:left-[5.5rem]">
<Image
className="w-full"
src="/images/join/shapes-1.svg"
width={317}
height={293}
alt="Shapes 1"
/>
</div>
<div className="absolute right-[15rem] -bottom-[7rem] -z-1 w-[28.1875rem] pointer-events-none lg:right-7 lg:-bottom-[5rem]">
<Image
className="w-full"
src="/images/join/shapes-2.svg"
width={451}
height={266}
alt="Shapes 2"
/>
</div>
</Section>
);
export default Join;

0
src/components/Layout/MainPanel.js Normal file → Executable file
View File

0
src/components/Layout/PanelContainer.js Normal file → Executable file
View File

0
src/components/Layout/PanelContent.js Normal file → Executable file
View File

View File

@@ -0,0 +1,53 @@
import Image from "../Image";
const Logos = ({ className }) => (
<div className={className}>
<h5 className="tagline mb-6 text-center text-n-1/50">
Helping people create beautiful content at
</h5>
<ul className="flex">
<li className="flex items-center justify-center flex-1 h-[8.5rem]">
<Image
src="/images/yourlogo.svg"
width={134}
height={28}
alt="Logo 3"
/>
</li>
<li className="flex items-center justify-center flex-1 h-[8.5rem]">
<Image
src="/images/yourlogo.svg"
width={134}
height={28}
alt="Logo 3"
/>
</li>
<li className="flex items-center justify-center flex-1 h-[8.5rem]">
<Image
src="/images/yourlogo.svg"
width={134}
height={28}
alt="Logo 3"
/>
</li>
<li className="flex items-center justify-center flex-1 h-[8.5rem]">
<Image
src="/images/yourlogo.svg"
width={134}
height={28}
alt="Logo 3"
/>
</li>
<li className="flex items-center justify-center flex-1 h-[8.5rem]">
<Image
src="/images/yourlogo.svg"
width={134}
height={28}
alt="Logo 3"
/>
</li>
</ul>
</div>
);
export default Logos;

0
src/components/Map/Map.css Normal file → Executable file
View File

0
src/components/Map/Map.js Normal file → Executable file
View File

0
src/components/Menu/ItemContent.js Normal file → Executable file
View File

0
src/components/Navbars/AdminNavbar.js Normal file → Executable file
View File

0
src/components/Navbars/AdminNavbarLinks.js Normal file → Executable file
View File

0
src/components/Navbars/AuthNavbar.js Normal file → Executable file
View File

File diff suppressed because it is too large Load Diff

0
src/components/Navbars/SearchBar/SearchBar.js Normal file → Executable file
View File

0
src/components/Navbars/Settings/SettingsBar.js Normal file → Executable file
View File

View File

@@ -0,0 +1,49 @@
import Image from "../Image";
const Notification = ({ className, title }) => (
<div
className={`flex items-center p-4 pr-6 bg-[#474060]/40 backdrop-blur border border-n-1/10 rounded-2xl ${
className || ""
}`}
>
<div className="mr-5">
<Image
className="w-full rounded-xl"
src="/images/notification/image-1.png"
width={52}
height={52}
alt="Image"
/>
</div>
<div className="flex-1">
<h6 className="mb-1 font-semibold text-base">{title}</h6>
<div className="flex items-center justify-between">
<ul className="flex -m-0.5">
{[
"/images/notification/image-4.png",
"/images/notification/image-3.png",
"/images/notification/image-2.png",
].map((item, index) => (
<li
className={`flex w-6 h-6 border-2 border-[#2E2A41] rounded-full overflow-hidden ${
index !== 0 ? "-ml-2" : ""
}`}
key={index}
>
<Image
className="w-full"
src={item}
width={20}
height={20}
alt={item}
/>
</li>
))}
</ul>
<div className="body-2 text-[#6C7275]">1m ago</div>
</div>
</div>
</div>
);
export default Notification;

0
src/components/PinInput/PinInput.js Normal file → Executable file
View File

View File

@@ -0,0 +1,138 @@
import { useRef, useState } from "react";
import { Splide, SplideTrack, SplideSlide } from "@splidejs/react-splide";
import Button from "@/components/Button";
import Image from "@/components/Image";
import { pricing } from "@/mocks/pricing";
type PricingListProps = {
monthly?: boolean;
};
const PricingList = ({ monthly = true }: PricingListProps) => {
const [activeIndex, setActiveIndex] = useState<number>(0);
const ref = useRef<any>(null);
const handleClick = (index: number) => {
setActiveIndex(index);
ref.current?.go(index);
};
return (
<Splide
className="splide-pricing splide-visible"
options={{
mediaQuery: "min",
autoWidth: true,
pagination: false,
arrows: false,
gap: "1rem",
breakpoints: {
1024: {
destroy: true,
},
},
}}
onMoved={(e, newIndex) => setActiveIndex(newIndex)}
hasTrack={false}
ref={ref}
>
<SplideTrack>
{pricing.map((item, index) => (
<SplideSlide
className={`${index === 1 ? "" : "py-3"}`}
key={item.id}
>
<div
className={`w-[19rem] h-full px-6 ${
index === 1 ? "py-12" : "py-8"
} bg-n-8 border border-n-6 rounded-[2rem] lg:w-auto`}
key={item.id}
>
<h4
className={`h4 mb-4 ${
index === 0 ? "text-color-2" : ""
} ${index === 1 ? "text-color-1" : ""} ${
index === 2 ? "text-color-3" : ""
}`}
>
{item.title}
</h4>
<p className="body-2 min-h-[4rem] mb-3 text-n-1/50">
{item.description}
</p>
<div className="flex items-center h-[5.5rem] mb-6">
{item.price && (
<>
<div className="h3">$</div>
<div className="text-[5.5rem] leading-none font-bold">
{monthly
? item.price
: item.price !== "0"
? (
+item.price *
12 *
0.9
).toFixed(1)
: item.price}
</div>
</>
)}
</div>
<Button
className="w-full mb-6"
href={
item.price
? "/pricing"
: "mailto:info@ui8.net"
}
white={!!item.price}
>
{item.price ? "Get started" : "Contact us"}
</Button>
<ul>
{item.features.map((feature, index) => (
<li
className="flex items-start py-5 border-t border-n-6"
key={index}
>
<Image
src="/images/check.svg"
width={24}
height={24}
alt="Check"
/>
<p className="body-2 ml-4">{feature}</p>
</li>
))}
</ul>
</div>
</SplideSlide>
))}
</SplideTrack>
<div className="flex justify-center mt-8 -mx-2 md:mt-15 lg:hidden">
{pricing.map((item, index) => (
<button
className="relative w-6 h-6 mx-2"
onClick={() => handleClick(index)}
key={item.id}
>
<span
className={`absolute inset-0 bg-conic-gradient rounded-full transition-opacity ${
index === activeIndex
? "opacity-100"
: "opacity-0"
}`}
></span>
<span className="absolute inset-0.25 bg-n-8 rounded-full">
<span className="absolute inset-2 bg-n-1 rounded-full"></span>
</span>
</button>
))}
</div>
</Splide>
);
};
export default PricingList;

19
src/components/ProtectedRoute.js Normal file → Executable file
View File

@@ -25,17 +25,26 @@ const ProtectedRoute = ({ children }) => {
);
}
// 记录当前路径,登录后可以回到这里
let currentPath = window.location.pathname + window.location.search;
let redirectUrl = `/auth/signin?redirect=${encodeURIComponent(currentPath)}`;
// 检查是否已登录
if (!isAuthenticated || !user) {
// 记录当前路径,登录后可以回到这里
const currentPath = window.location.pathname + window.location.search;
const redirectUrl = `/auth/signin?redirect=${encodeURIComponent(currentPath)}`;
return <Navigate to={redirectUrl} replace />;
}
// 已登录,渲染子组件
return children;
// return children;
// 更新逻辑 如果 currentPath 是首页 登陆成功后跳转到个人中心
if (currentPath === '/' || currentPath === '/home') {
currentPath = '/profile';
redirectUrl = `/auth/signin?redirect=${encodeURIComponent(currentPath)}`;
return <Navigate to={redirectUrl} replace />;
} else { // 否则正常渲染
return children;
}
};
export default ProtectedRoute;

0
src/components/RTLProvider/RTLProvider.js Normal file → Executable file
View File

0
src/components/Scrollbar/Scrollbar.js Normal file → Executable file
View File

View File

@@ -0,0 +1,57 @@
const Section = ({
className,
crosses,
crossesOffset,
customPaddings,
children,
}) => (
<div
className={`relative ${
customPaddings ||
`py-10 lg:py-16 xl:py-20 ${crosses ? "lg:py-32 xl:py-40" : ""}`
} ${className || ""}`}
>
{children}
<div className="hidden absolute top-0 left-5 w-0.25 h-full bg-stroke-1 pointer-events-none md:block lg:left-7.5 xl:left-10"></div>
<div className="hidden absolute top-0 right-5 w-0.25 h-full bg-stroke-1 pointer-events-none md:block lg:right-7.5 xl:right-10"></div>
{crosses && (
<>
<div
className={`hidden absolute top-0 left-7.5 right-7.5 h-0.25 bg-stroke-1 ${
crossesOffset && crossesOffset
} pointer-events-none lg:block xl:left-10 right-10`}
></div>
<svg
className={`hidden absolute -top-[0.3125rem] left-[1.5625rem] ${
crossesOffset && crossesOffset
} pointer-events-none lg:block xl:left-[2.1875rem]`}
xmlns="http://www.w3.org/2000/svg"
width="11"
height="11"
fill="none"
>
<path
d="M7 1a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h2a1 1 0 0 1 1 1v2a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V8a1 1 0 0 1 1-1h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H8a1 1 0 0 1-1-1V1z"
fill="#ada8c4"
/>
</svg>
<svg
className={`hidden absolute -top-[0.3125rem] right-[1.5625rem] ${
crossesOffset && crossesOffset
} pointer-events-none lg:block xl:right-[2.1875rem]`}
xmlns="http://www.w3.org/2000/svg"
width="11"
height="11"
fill="none"
>
<path
d="M7 1a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h2a1 1 0 0 1 1 1v2a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V8a1 1 0 0 1 1-1h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H8a1 1 0 0 1-1-1V1z"
fill="#ada8c4"
/>
</svg>
</>
)}
</div>
);
export default Section;

0
src/components/Separator/Separator.js Normal file → Executable file
View File

View File

@@ -0,0 +1,195 @@
import Section from "@/components/Section";
import Generating from "@/components/Generating";
import Image from "@/components/Image";
import Heading from "@/components/Heading";
type ServicesProps = {
containerClassName?: string;
};
const Services = ({ containerClassName }: ServicesProps) => (
<Section>
<div className={`container ${containerClassName || ""}`}>
<Heading
title="Generative AI made for creators."
text="Brainwave unlocks the potential of AI-powered applications"
/>
<div className="relative">
<div className="relative z-1 flex items-center h-[38.75rem] mb-5 p-8 border border-n-1/10 rounded-3xl overflow-hidden lg:h-[38.75rem] lg:p-20 xl:h-[45.75rem]">
<div className="absolute top-0 left-0 w-full h-full pointer-events-none md:w-3/5 xl:w-auto">
<Image
className="w-full h-full object-cover md:object-right"
src="/images/services/service-1.png"
width={797}
height={733}
alt="Smartest AI"
/>
</div>
<div className="relative z-1 max-w-[17rem] ml-auto">
<h4 className="h4 mb-4">Smartest AI</h4>
<p className="bpdy-2 mb-[3.125rem] text-n-3">
Brainwave unlocks the potential of AI-powered
applications
</p>
<ul className="body-2">
{[
"Photo generating",
"Photo enhance",
"Seamless Integration",
].map((item, index) => (
<li
className="flex items-start py-4 border-t border-n-6"
key={index}
>
<Image
src="/images/check.svg"
width={24}
height={24}
alt="Check"
/>
<p className="ml-4">{item}</p>
</li>
))}
</ul>
</div>
<Generating className="absolute left-4 right-4 bottom-4 border border-n-1/10 lg:left-1/2 lg-right-auto lg:bottom-8 lg:-translate-x-1/2" />
</div>
<div className="relative z-1 grid gap-5 lg:grid-cols-2">
<div className="relative min-h-[38.75rem] border border-n-1/10 rounded-3xl overflow-hidden">
<div className="absolute inset-0">
<Image
className="w-full h-full object-cover"
src="/images/services/service-2.png"
width={630}
height={748}
alt="Smartest AI"
/>
</div>
<div className="absolute inset-0 flex flex-col justify-end p-8 bg-gradient-to-b from-n-8/0 to-n-8/90 lg:p-15">
<h4 className="h4 mb-4">Photo editing</h4>
<p className="body-2 text-n-3">
{`Automatically enhance your photos using our AI app's
photo editing feature. Try it now!`}
</p>
</div>
<div className="absolute top-8 right-8 max-w-[17.5rem] py-6 px-8 bg-black rounded-t-xl rounded-bl-xl font-code text-base lg:top-16 lg:right-[8.75rem] lg:max-w-[17.5rem]">
Hey Brainwave, enhance this photo
<svg
className="absolute left-full bottom-0"
xmlns="http://www.w3.org/2000/svg"
width="26"
height="37"
>
<path d="M21.843 37.001c3.564 0 5.348-4.309 2.829-6.828L3.515 9.015A12 12 0 0 1 0 .53v36.471h21.843z" />
</svg>
</div>
</div>
<div className="p-4 bg-n-7 rounded-3xl overflow-hidden lg:min-h-[45.75rem]">
<div className="py-12 px-4 xl:px-8">
<h4 className="h4 mb-4">Video generation</h4>
<p className="body-2 mb-[2.25rem] text-n-3">
The worlds most powerful AI photo and video art
generation engine.What will you create?
</p>
<ul className="flex items-center justify-between">
{[
"/images/icons/recording-03.svg",
"/images/icons/recording-01.svg",
"/images/icons/disc-02.svg",
"/images/icons/chrome-cast.svg",
"/images/icons/sliders-04.svg",
].map((item, index) => (
<li
className={`flex items-center justify-center ${
index === 2
? "w-[3rem] h-[3rem] p-0.25 bg-conic-gradient rounded-2xl md:w-[4.5rem] md:h-[4.5rem]"
: "flex w-10 h-10 bg-n-6 rounded-2xl md:w-15 md:h-15"
}`}
key={index}
>
<div
className={
index === 2
? "flex items-center justify-center w-full h-full bg-n-7 rounded-[0.9375rem]"
: ""
}
>
<Image
src={item}
width={24}
height={24}
alt={item}
/>
</div>
</li>
))}
</ul>
</div>
<div className="relative h-[20.5rem] bg-n-8 rounded-xl overflow-hidden md:h-[25rem]">
<Image
className="w-full h-full object-cover"
src="/images/services/service-3.png"
width={517}
height={400}
alt="Smartest AI"
/>
<div className="absolute top-8 left-[3.125rem] w-full max-w-[14rem] pt-2.5 pr-2.5 pb-7 pl-5 bg-n-6 rounded-t-xl rounded-br-xl font-code text-base md:max-w-[17.5rem]">
Video generated!
<div className="absolute left-5 -bottom-[1.125rem] flex items-center justify-center w-[2.25rem] h-[2.25rem] bg-color-1 rounded-[0.75rem]">
<Image
src="/images/brainwave-symbol-white.svg"
width={26}
height={26}
alt="Brainwave"
/>
</div>
<div className="tagline absolute right-2.5 bottom-1 text-[0.625rem] text-n-3 uppercase">
just now
</div>
<svg
className="absolute right-full bottom-0 -scale-x-100"
xmlns="http://www.w3.org/2000/svg"
width="26"
height="37"
>
<path
className="fill-n-6"
d="M21.843 37.001c3.564 0 5.348-4.309 2.829-6.828L3.515 9.015A12 12 0 0 1 0 .53v36.471h21.843z"
/>
</svg>
</div>
<div className="absolute left-0 bottom-0 w-full flex items-center p-6">
<svg
className="mr-3"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
>
<path
d="M8.006 2.802l.036.024 10.549 7.032.805.567c.227.183.494.437.648.808a2 2 0 0 1 0 1.532c-.154.371-.421.625-.648.808-.217.175-.5.364-.805.567L8.006 21.198l-.993.627c-.285.154-.676.331-1.132.303a2 2 0 0 1-1.476-.79c-.276-.365-.346-.788-.375-1.111S4 19.502 4 19.054V4.99v-.043l.029-1.174c.03-.323.1-.746.375-1.11a2 2 0 0 1 1.476-.79c.456-.027.847.149 1.132.304s.62.378.993.627z"
fill="#fff"
/>
</svg>
<div className="flex-1 bg-[#D9D9D9]">
<div className="w-1/2 h-0.5 bg-color-1"></div>
</div>
</div>
</div>
</div>
</div>
<div className="absolute top-0 -left-[10rem] w-[56.625rem] h-[56.625rem] opacity-50 mix-blend-color-dodge pointer-events-none">
<Image
className="absolute top-1/2 left-1/2 w-[79.5625rem] max-w-[79.5625rem] h-[88.5625rem] -translate-x-1/2 -translate-y-1/2"
src="/images/gradient.png"
width={1417}
height={1417}
alt="Gradient"
/>
</div>
</div>
</div>
</Section>
);
export default Services;

0
src/components/Sidebar/Sidebar.js Normal file → Executable file
View File

0
src/components/Sidebar/SidebarDocs.js Normal file → Executable file
View File

0
src/components/Tables/BasicTable.js Normal file → Executable file
View File

0
src/components/Tables/BillingRow.js Normal file → Executable file
View File

0
src/components/Tables/DashboardTableRow.js Normal file → Executable file
View File

0
src/components/Tables/InvoicesRow.js Normal file → Executable file
View File

0
src/components/Tables/SearchTable1.js Normal file → Executable file
View File

0
src/components/Tables/SearchTable2.js Normal file → Executable file
View File

0
src/components/Tables/TablesProjectRow.js Normal file → Executable file
View File

0
src/components/Tables/TablesReportsRow.js Normal file → Executable file
View File

0
src/components/Tables/TablesTableRow.js Normal file → Executable file
View File

0
src/components/Tables/TimelineRow.js Normal file → Executable file
View File

0
src/components/Tables/TransactionRow.js Normal file → Executable file
View File

0
src/components/VisxPieChart/VisxPieChart.js Normal file → Executable file
View File

View File

@@ -0,0 +1,570 @@
// src/contexts/AuthContext.js - Session版本
import React, { createContext, useContext, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useToast } from '@chakra-ui/react';
// API基础URL配置
const isProduction = process.env.NODE_ENV === 'production';
const API_BASE_URL = isProduction ? "" : process.env.REACT_APP_API_URL || "http://49.232.185.254:5000";
// 创建认证上下文
const AuthContext = createContext();
// 自定义Hook
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
// 认证提供者组件
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const navigate = useNavigate();
const toast = useToast();
// 检查Session状态
const checkSession = async () => {
try {
console.log('🔍 检查Session状态...');
const response = await fetch(`${API_BASE_URL}/api/auth/session`, {
method: 'GET',
credentials: 'include', // 重要包含cookie
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) {
throw new Error('Session检查失败');
}
const data = await response.json();
console.log('📦 Session数据:', data);
if (data.isAuthenticated && data.user) {
setUser(data.user);
setIsAuthenticated(true);
} else {
setUser(null);
setIsAuthenticated(false);
}
} catch (error) {
console.error('❌ Session检查错误:', error);
setUser(null);
setIsAuthenticated(false);
} finally {
setIsLoading(false);
}
};
// 初始化时检查Session
useEffect(() => {
checkSession();
}, []);
// 监听路由变化检查session处理微信登录回调
useEffect(() => {
const handleRouteChange = () => {
// 如果是从微信回调返回的重新检查session
if (window.location.pathname === '/home' && !isAuthenticated) {
checkSession();
}
};
window.addEventListener('popstate', handleRouteChange);
return () => window.removeEventListener('popstate', handleRouteChange);
}, [isAuthenticated]);
// 更新本地用户的便捷方法
const updateUser = (partial) => {
setUser((prev) => ({ ...(prev || {}), ...partial }));
};
// 传统登录方法
const login = async (credential, password, loginType = 'email') => {
try {
setIsLoading(true);
console.log('🔐 开始登录流程:', { credential, loginType });
const formData = new URLSearchParams();
formData.append('password', password);
if (loginType === 'username') {
formData.append('username', credential);
} else if (loginType === 'email') {
formData.append('email', credential);
} else if (loginType === 'phone') {
formData.append('username', credential);
}
console.log('📤 发送登录请求到:', `${API_BASE_URL}/api/auth/login`);
console.log('📝 请求数据:', {
credential,
loginType,
formData: formData.toString()
});
const response = await fetch(`${API_BASE_URL}/api/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
credentials: 'include', // 包含cookie
body: formData
});
console.log('📨 响应状态:', response.status, response.statusText);
console.log('📨 响应头:', Object.fromEntries(response.headers.entries()));
// 获取响应文本然后尝试解析JSON
const responseText = await response.text();
console.log('📨 响应原始内容:', responseText);
let data;
try {
data = JSON.parse(responseText);
console.log('✅ JSON解析成功:', data);
} catch (parseError) {
console.error('❌ JSON解析失败:', parseError);
console.error('📄 响应内容:', responseText);
throw new Error(`服务器响应格式错误: ${responseText.substring(0, 100)}...`);
}
if (!response.ok || !data.success) {
throw new Error(data.error || '登录失败');
}
// 更新状态
setUser(data.user);
setIsAuthenticated(true);
toast({
title: "登录成功",
description: "欢迎回来!",
status: "success",
duration: 3000,
isClosable: true,
});
return { success: true };
} catch (error) {
console.error('❌ 登录错误:', error);
toast({
title: "登录失败",
description: error.message || "请检查您的登录信息",
status: "error",
duration: 3000,
isClosable: true,
});
return { success: false, error: error.message };
} finally {
setIsLoading(false);
}
};
// 注册方法
const register = async (username, email, password) => {
try {
setIsLoading(true);
const formData = new URLSearchParams();
formData.append('username', username);
formData.append('email', email);
formData.append('password', password);
const response = await fetch(`${API_BASE_URL}/api/auth/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
credentials: 'include',
body: formData
});
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.error || '注册失败');
}
// 注册成功后自动登录
setUser(data.user);
setIsAuthenticated(true);
toast({
title: "注册成功",
description: "欢迎加入价值前沿!",
status: "success",
duration: 3000,
isClosable: true,
});
return { success: true };
} catch (error) {
console.error('注册错误:', error);
toast({
title: "注册失败",
description: error.message || "注册失败,请稍后重试",
status: "error",
duration: 3000,
isClosable: true,
});
return { success: false, error: error.message };
} finally {
setIsLoading(false);
}
};
// 手机号注册
const registerWithPhone = async (phone, code, username, password) => {
try {
setIsLoading(true);
const response = await fetch(`${API_BASE_URL}/api/auth/register/phone`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
phone,
code,
username,
password
})
});
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.error || '注册失败');
}
// 注册成功后自动登录
setUser(data.user);
setIsAuthenticated(true);
toast({
title: "注册成功",
description: "欢迎加入价值前沿!",
status: "success",
duration: 3000,
isClosable: true,
});
return { success: true };
} catch (error) {
console.error('手机注册错误:', error);
toast({
title: "注册失败",
description: error.message || "注册失败,请稍后重试",
status: "error",
duration: 3000,
isClosable: true,
});
return { success: false, error: error.message };
} finally {
setIsLoading(false);
}
};
// 邮箱注册
const registerWithEmail = async (email, code, username, password) => {
try {
setIsLoading(true);
const response = await fetch(`${API_BASE_URL}/api/auth/register/email`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
email,
code,
username,
password
})
});
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.error || '注册失败');
}
// 注册成功后自动登录
setUser(data.user);
setIsAuthenticated(true);
toast({
title: "注册成功",
description: "欢迎加入价值前沿!",
status: "success",
duration: 3000,
isClosable: true,
});
return { success: true };
} catch (error) {
console.error('邮箱注册错误:', error);
toast({
title: "注册失败",
description: error.message || "注册失败,请稍后重试",
status: "error",
duration: 3000,
isClosable: true,
});
return { success: false, error: error.message };
} finally {
setIsLoading(false);
}
};
// 发送手机验证码
const sendSmsCode = async (phone) => {
try {
const response = await fetch(`${API_BASE_URL}/api/auth/send-sms-code`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone })
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || '发送失败');
}
toast({
title: "验证码已发送",
description: "请查收短信",
status: "success",
duration: 3000,
isClosable: true,
});
return { success: true };
} catch (error) {
console.error('SMS code error:', error);
toast({
title: "发送失败",
description: error.message || "请稍后重试",
status: "error",
duration: 3000,
isClosable: true,
});
return { success: false, error: error.message };
}
};
// 发送邮箱验证码
const sendEmailCode = async (email) => {
try {
const response = await fetch(`${API_BASE_URL}/api/auth/send-email-code`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email })
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || '发送失败');
}
toast({
title: "验证码已发送",
description: "请查收邮件",
status: "success",
duration: 3000,
isClosable: true,
});
return { success: true };
} catch (error) {
console.error('Email code error:', error);
toast({
title: "发送失败",
description: error.message || "请稍后重试",
status: "error",
duration: 3000,
isClosable: true,
});
return { success: false, error: error.message };
}
};
// 登出方法
const logout = async () => {
try {
// 调用后端登出API
await fetch(`${API_BASE_URL}/api/auth/logout`, {
method: 'POST',
credentials: 'include'
});
// 清除本地状态
setUser(null);
setIsAuthenticated(false);
toast({
title: "已登出",
description: "您已成功退出登录",
status: "info",
duration: 2000,
isClosable: true,
});
// 跳转到登录页面
navigate('/auth/signin');
} catch (error) {
console.error('Logout error:', error);
// 即使API调用失败也清除本地状态
setUser(null);
setIsAuthenticated(false);
navigate('/auth/signin');
}
};
// 检查用户是否有特定权限
const hasRole = (role) => {
return user && user.role === role;
};
// 检查用户订阅权限
const hasSubscriptionPermission = async (featureName) => {
try {
// 如果用户未登录,返回免费权限
if (!isAuthenticated) {
const freePermissions = {
'related_stocks': false,
'related_concepts': false,
'transmission_chain': false,
'historical_events': 'limited'
};
return freePermissions[featureName] || false;
}
// 获取用户权限信息
const response = await fetch(`${API_BASE_URL}/api/subscription/permissions`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) {
throw new Error('获取权限信息失败');
}
const data = await response.json();
if (data.success && data.data) {
return data.data.permissions[featureName] || false;
}
// 如果API调用失败返回默认权限
const defaultPermissions = {
'related_stocks': user?.subscription_type === 'pro' || user?.subscription_type === 'max',
'related_concepts': user?.subscription_type === 'pro' || user?.subscription_type === 'max',
'transmission_chain': user?.subscription_type === 'max',
'historical_events': user?.subscription_type === 'pro' || user?.subscription_type === 'max' ? 'full' : 'limited'
};
return defaultPermissions[featureName] || false;
} catch (error) {
console.error('检查订阅权限失败:', error);
// 降级处理:基于用户现有的订阅信息
const fallbackPermissions = {
'related_stocks': user?.subscription_type === 'pro' || user?.subscription_type === 'max',
'related_concepts': user?.subscription_type === 'pro' || user?.subscription_type === 'max',
'transmission_chain': user?.subscription_type === 'max',
'historical_events': user?.subscription_type === 'pro' || user?.subscription_type === 'max' ? 'full' : 'limited'
};
return fallbackPermissions[featureName] || false;
}
};
// 获取用户订阅级别
const getSubscriptionLevel = () => {
if (!isAuthenticated) return 'free';
return user?.subscription_type || 'free';
};
// 检查是否需要升级订阅
const requiresUpgrade = (requiredLevel) => {
const currentLevel = getSubscriptionLevel();
const levels = { 'free': 0, 'pro': 1, 'max': 2 };
const currentLevelValue = levels[currentLevel] || 0;
const requiredLevelValue = levels[requiredLevel] || 0;
return currentLevelValue < requiredLevelValue;
};
// 刷新session可选
const refreshSession = async () => {
await checkSession();
};
// 提供给子组件的值
const value = {
user,
isAuthenticated,
isLoading,
updateUser,
login,
register,
registerWithPhone,
registerWithEmail,
sendSmsCode,
sendEmailCode,
logout,
hasRole,
hasSubscriptionPermission,
getSubscriptionLevel,
requiresUpgrade,
refreshSession,
checkSession
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};

View File

@@ -0,0 +1,20 @@
/*!
=========================================================
* Argon Dashboard Chakra PRO - v1.0.0
=========================================================
* Product Page: https://www.creative-tim.com/product/argon-dashboard-chakra-pro
* Copyright 2022 Creative Tim (https://www.creative-tim.com/)
* Designed and Coded by Simmmple & Creative Tim
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
import { createContext } from "react";
export const SidebarContext = createContext();