├── src ├── _redux │ ├── index.ts │ ├── rootReducer.ts │ └── store.ts ├── utils │ ├── noop.ts │ ├── setTitle.ts │ ├── logout.ts │ ├── randomString.ts │ ├── index.ts │ ├── assetsHelper.ts │ ├── imageCrop.ts │ └── formatData.ts ├── component │ ├── index.ts │ ├── Avatar │ │ ├── styled.ts │ │ └── index.tsx │ ├── CommentList │ │ ├── styled.ts │ │ └── index.tsx │ ├── CommentCard │ │ ├── tinyIcon.ts │ │ └── styled.ts │ └── CommentInput │ │ ├── styled.ts │ │ └── index.tsx ├── features │ ├── index.ts │ ├── Comment │ │ └── index.tsx │ ├── Notification │ │ ├── Styled.ts │ │ └── index.tsx │ └── Adblock │ │ └── index.tsx ├── layout │ ├── index.ts │ ├── frame │ │ ├── AppFrame.tsx │ │ ├── Footer.tsx │ │ └── Header.tsx │ └── core │ │ ├── ThemeProvider.tsx │ │ └── SplashScreen.tsx ├── Icon │ ├── index.tsx │ ├── BackOldIcon.tsx │ ├── MagnetIcon.tsx │ ├── svg │ │ ├── ie.svg │ │ ├── xiaomi.svg │ │ └── unknown.svg │ ├── Ed2kIcon.tsx │ └── DoubanRateIcon.tsx ├── hooks │ ├── index.ts │ ├── useAuth.ts │ ├── useRedux.ts │ ├── useLoginBack.ts │ ├── useGoResourcePage.ts │ └── useDomeSize.ts ├── app │ ├── pages │ │ ├── motFound │ │ │ └── index.tsx │ │ ├── index.ts │ │ ├── login │ │ │ └── userSlice.ts │ │ ├── home │ │ │ ├── LastComment │ │ │ │ ├── Styled.ts │ │ │ │ └── index.tsx │ │ │ ├── Announce │ │ │ │ └── index.tsx │ │ │ ├── Styled.ts │ │ │ ├── LastResource │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── discuss │ │ │ └── index.tsx │ │ ├── search │ │ │ ├── Rank.tsx │ │ │ ├── Section.tsx │ │ │ ├── CommentDrawer.tsx │ │ │ ├── SubtitleDrawer.tsx │ │ │ └── index.tsx │ │ ├── resource │ │ │ ├── DownloadBtn.tsx │ │ │ ├── Address.tsx │ │ │ └── index.tsx │ │ ├── help │ │ │ └── index.tsx │ │ ├── me │ │ │ └── avatarUploader.tsx │ │ └── database │ │ │ └── index.tsx │ ├── modules │ │ └── statistics │ │ │ ├── styled.ts │ │ │ ├── index.tsx │ │ │ └── Visitor.tsx │ ├── themeSlice.ts │ ├── Routes.tsx │ ├── App.tsx │ └── BasePage.tsx ├── API │ ├── index.ts │ ├── database.ts │ ├── announce.ts │ ├── notification.ts │ ├── axiosConfig.ts │ ├── metrics.ts │ ├── user.ts │ ├── search.ts │ ├── comment.ts │ └── resource.ts ├── setupProxy.js ├── index.tsx ├── material.d.ts └── react-app-env.d.ts ├── .sentryclirc ├── src-tauri ├── build.rs ├── src │ └── main.rs ├── Cargo.toml └── tauri.conf.json ├── public ├── logo.icns ├── logo.ico ├── logo.png ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── sponsor │ ├── afdian.png │ ├── coffee.jpg │ └── stripe.png ├── manifest.json ├── index.css ├── svg │ ├── mongodb.svg │ ├── sqlite.svg │ ├── logo.svg │ ├── mysql.svg │ ├── login.svg │ └── emptyAddress.svg └── index.html ├── Makefile ├── .github ├── dependabot.yml └── workflows │ └── builder.yml ├── .env.example ├── .gitignore ├── README.md ├── tsconfig.json └── package.json /src/_redux/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./store"; 2 | -------------------------------------------------------------------------------- /src/utils/noop.ts: -------------------------------------------------------------------------------- 1 | export const noop = () => {}; 2 | -------------------------------------------------------------------------------- /.sentryclirc: -------------------------------------------------------------------------------- 1 | [defaults] 2 | project=yyets 3 | org=yyets 4 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /public/logo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/YYeTsFE/HEAD/public/logo.icns -------------------------------------------------------------------------------- /public/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/YYeTsFE/HEAD/public/logo.ico -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/YYeTsFE/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/YYeTsFE/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/YYeTsFE/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/YYeTsFE/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | git tag $$(git rev-parse --short HEAD) 3 | git push 4 | git push --tags -v 5 | -------------------------------------------------------------------------------- /public/sponsor/afdian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/YYeTsFE/HEAD/public/sponsor/afdian.png -------------------------------------------------------------------------------- /public/sponsor/coffee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/YYeTsFE/HEAD/public/sponsor/coffee.jpg -------------------------------------------------------------------------------- /public/sponsor/stripe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/YYeTsFE/HEAD/public/sponsor/stripe.png -------------------------------------------------------------------------------- /src/component/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CommentInput"; 2 | export * from "./CommentList"; 3 | export * from "./Avatar"; 4 | -------------------------------------------------------------------------------- /src/features/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Comment"; 2 | export * from "./Notification"; 3 | export * from "./Adblock"; 4 | -------------------------------------------------------------------------------- /src/utils/setTitle.ts: -------------------------------------------------------------------------------- 1 | export function setTitle(title: string) { 2 | document.title = `${title } - 人人影视下载分享站`; 3 | } 4 | -------------------------------------------------------------------------------- /src/layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./core/ThemeProvider"; 2 | export * from "./core/SplashScreen"; 3 | 4 | export * from "./frame/AppFrame"; 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /src/Icon/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Ed2kIcon"; 2 | export * from "./MagnetIcon"; 3 | export * from "./DoubanRateIcon"; 4 | export * from "./BackOldIcon"; 5 | -------------------------------------------------------------------------------- /src/utils/logout.ts: -------------------------------------------------------------------------------- 1 | import Cookies from "js-cookie"; 2 | 3 | export function logout() { 4 | Cookies.remove("username"); 5 | localStorage.removeItem("username"); 6 | } 7 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./useLoginBack"; 2 | export * from "./useRedux"; 3 | export * from "./useAuth"; 4 | export * from "./useDomeSize"; 5 | export * from "./useGoResourcePage"; 6 | -------------------------------------------------------------------------------- /src/app/pages/motFound/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { setTitle } from "utils"; 3 | 4 | export function NotFoundPage() { 5 | setTitle("404"); 6 | return
404
; 7 | } 8 | -------------------------------------------------------------------------------- /src/API/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./search"; 2 | export * from "./resource"; 3 | export * from "./user"; 4 | export * from "./database"; 5 | export * from "./metrics"; 6 | export * from "./comment"; 7 | export * from "./notification"; 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # 接口域名 2 | REACT_APP_DOMAIN=https://yyets.click 3 | # google analytic 追踪 ID 4 | REACT_APP_GA=G-K76PSKSSGX 5 | # 是否生成 sourcemap 6 | GENERATE_SOURCEMAP=false 7 | # adsense pub id,如果不想显示广告请注释这一行 8 | REACT_APP_ADSENSE=2988254457061384 9 | -------------------------------------------------------------------------------- /src/hooks/useAuth.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useAppSelector } from "./useRedux"; 3 | 4 | export const useAuth = () => { 5 | const { username } = useAppSelector((state) => state.user); 6 | 7 | return React.useMemo(() => ({ username }), [username]); 8 | }; 9 | -------------------------------------------------------------------------------- /src/app/modules/statistics/styled.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles, Theme } from "@material-ui/core"; 2 | 3 | export const useStyles = makeStyles((theme: Theme) => 4 | createStyles({ 5 | container: { 6 | paddingTop: theme.spacing(4), 7 | }, 8 | }) 9 | ); 10 | -------------------------------------------------------------------------------- /src/app/pages/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./home"; 2 | export * from "./motFound"; 3 | export * from "./search"; 4 | export * from "./resource"; 5 | export * from "./login"; 6 | export * from "./discuss"; 7 | export * from "./me"; 8 | export * from "./database"; 9 | export * from "./help"; 10 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | fn main() { 7 | tauri::Builder::default() 8 | .run(tauri::generate_context!()) 9 | .expect("error while running tauri application"); 10 | } 11 | -------------------------------------------------------------------------------- /src/hooks/useRedux.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; 2 | 3 | import { AppDispatch, RootState } from "_redux"; 4 | 5 | export const useAppDispatch = () => useDispatch(); 6 | 7 | export const useAppSelector: TypedUseSelectorHook = useSelector; 8 | -------------------------------------------------------------------------------- /src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const { createProxyMiddleware } = require("http-proxy-middleware"); 2 | 3 | module.exports = function setProxy(app) { 4 | app.use( 5 | "/api", 6 | createProxyMiddleware({ 7 | target: process.env.REACT_APP_DOMAIN, 8 | changeOrigin: true, 9 | }) 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import App from "./app/App"; 5 | import { SplashScreenProvider } from "./layout"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById("root") 12 | ); 13 | -------------------------------------------------------------------------------- /src/hooks/useLoginBack.ts: -------------------------------------------------------------------------------- 1 | import { LinkProps, useLocation } from "react-router-dom"; 2 | 3 | export function useLoginBack() { 4 | const location = useLocation(); 5 | 6 | const url: LinkProps<{ ref: string }>["to"] = { 7 | pathname: "/login", 8 | state: { ref: location.pathname + location.search }, 9 | }; 10 | 11 | return url; 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/randomString.ts: -------------------------------------------------------------------------------- 1 | export function randomString(len: number = 16) { 2 | const $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefhijlmnoprstuvwxyz1234567890"; 3 | const maxPos = $chars.length; 4 | let code = ""; 5 | // eslint-disable-next-line no-plusplus 6 | for (let i = 0; i < len; i++) { 7 | code += $chars.charAt(Math.floor(Math.random() * maxPos)); 8 | } 9 | return code; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | 3 | export * from "./assetsHelper"; 4 | export * from "./randomString"; 5 | export * from "./logout"; 6 | export * from "./setTitle"; 7 | export * from "./formatData"; 8 | export * from "./noop"; 9 | export * from "./imageCrop"; 10 | 11 | export function ShowAdsense(): boolean { 12 | const data = useSelector((state: any) => state.adsenseState.data); 13 | return !data?.includes(window.location.href); 14 | } 15 | -------------------------------------------------------------------------------- /src/Icon/BackOldIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SvgIcon, SvgIconProps } from "@material-ui/core"; 3 | 4 | export function BackOldIcon(props: SvgIconProps) { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .idea 26 | /.env 27 | /src-tauri/icons/* 28 | /src-tauri/.gitignore 29 | -------------------------------------------------------------------------------- /src/API/database.ts: -------------------------------------------------------------------------------- 1 | import axios from "./axiosConfig"; 2 | 3 | export interface GetDatabaseRes { 4 | "yyets_mongo.gz": { 5 | checksum: string; 6 | date: string; 7 | size: string; 8 | }; 9 | "yyets_mysql.zip": { 10 | checksum: string; 11 | date: string; 12 | size: string; 13 | }; 14 | "yyets_sqlite.zip": { 15 | checksum: string; 16 | date: string; 17 | size: string; 18 | }; 19 | } 20 | 21 | export function getDatabase() { 22 | return axios.get("/api/db_dump"); 23 | } 24 | -------------------------------------------------------------------------------- /src/material.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@material-ui/core/styles/createTheme" { 2 | interface Theme { 3 | status: { 4 | danger: React.CSSProperties["color"]; 5 | }; 6 | } 7 | interface ThemeOptions { 8 | status: { 9 | danger: React.CSSProperties["color"]; 10 | }; 11 | } 12 | } 13 | 14 | declare module "@material-ui/core/styles/createPalette" { 15 | interface Palette { 16 | neutral: Palette["primary"]; 17 | } 18 | interface PaletteOptions { 19 | neutral: PaletteOptions["primary"]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/component/Avatar/styled.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles, Theme } from "@material-ui/core"; 2 | import { purple } from "@material-ui/core/colors"; 3 | 4 | export const useStyles = makeStyles((theme: Theme) => 5 | createStyles({ 6 | wrap: { 7 | position: "relative", 8 | width: 40, 9 | height: 40, 10 | }, 11 | avatar: { 12 | fontSize: "0.875rem", 13 | }, 14 | circle: { 15 | position: "absolute", 16 | bottom: -2, 17 | right: -2, 18 | height: 16, 19 | width: 16, 20 | }, 21 | }) 22 | ); 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YYeTs 2 | 3 | [YYeTsBot 前端](https://yyets.click/) 4 | 5 | React App 6 | 7 | 自适应 PC、移动端,亮色、暗色模式 8 | 9 | ## 部署 10 | 11 | ### 1. 安装依赖 12 | 13 | ```shell 14 | yarn 15 | ``` 16 | 17 | ### 2. 复制 `.env.example` 为 `.env`,修改其中的变量 18 | 19 | ```dotenv 20 | # 接口域名 21 | REACT_APP_DOMAIN=xxx 22 | # google analytic 追踪 ID 23 | REACT_APP_GA=xxx 24 | # 是否生成 sourcemap 25 | GENERATE_SOURCEMAP=true 26 | # adsense 27 | REACT_APP_ADSENSE=xxx 28 | ``` 29 | 30 | ### 3. build 项目 31 | 32 | ```shell 33 | yarn build 34 | ``` 35 | 36 | ## TODO 37 | 38 | - [ ] 升级到MUI v5 39 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "人人影视分享", 3 | "name": "人人影视分享下载站", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | type GtagEvent = 3 | | "login" // 登录 4 | | "logout" // 登出 5 | | "search" // 搜索 6 | | "download" // 下载 7 | | "multiDownload" // 批量下载 8 | | "copyComment" // 复制评论 9 | | "ResilioSync" // ResilioSync 10 | | "database" // 下载 11 | | "metrics" // 查看数据统计 12 | | "back_old" // 返回旧版 13 | | "comment" // 评论 14 | | "add_to_favorite" // 添加到收藏 15 | | "remove_from_favorite" // 从收藏移除 16 | | "timeout" // 请求超时 17 | | "share"; // 分享页面 18 | 19 | declare function gtag(event: "event", eventType: GtagEvent, option?: { [key: string]: any }); 20 | -------------------------------------------------------------------------------- /src/Icon/MagnetIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SvgIcon, SvgIconProps } from "@material-ui/core"; 3 | 4 | export function MagnetIcon(props: SvgIconProps) { 5 | return ( 6 | 7 | {" "} 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/API/announce.ts: -------------------------------------------------------------------------------- 1 | import axios from "./axiosConfig"; 2 | 3 | interface AnnounceParams { 4 | size: number; 5 | page: number; 6 | } 7 | 8 | export interface AnnounceObject { 9 | username: string; 10 | date: string; 11 | content: string; 12 | } 13 | 14 | export interface AnnounceRes { 15 | data: Array; 16 | count: number; 17 | } 18 | 19 | /* 获取公告 */ 20 | export function getAnnounce(params: AnnounceParams) { 21 | return axios.get("/api/announcement", { params }); 22 | } 23 | 24 | export function getNotification(params: AnnounceParams) { 25 | return axios.get("/api/notification", { params }); 26 | } 27 | -------------------------------------------------------------------------------- /src/app/themeSlice.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 3 | 4 | type AppearanceType = "light" | "dark" | "auto"; 5 | 6 | interface ThemeState { 7 | appearance: AppearanceType; 8 | } 9 | 10 | const initialState: ThemeState = { 11 | appearance: "auto", 12 | }; 13 | 14 | const themeSlice = createSlice({ 15 | name: "theme", 16 | initialState, 17 | reducers: { 18 | changeAppearance: (state, { payload }: PayloadAction) => { 19 | state.appearance = payload; 20 | }, 21 | }, 22 | }); 23 | 24 | export const themeReducer = themeSlice.reducer; 25 | -------------------------------------------------------------------------------- /src/utils/assetsHelper.ts: -------------------------------------------------------------------------------- 1 | export const toAbsoluteUrl = (pathname: string) => process.env.PUBLIC_URL + pathname; 2 | 3 | export function waitForElement(id: string) { 4 | return new Promise((resolve) => { 5 | if (document.getElementById(id)) { 6 | return resolve(document.getElementById(id)); 7 | } 8 | 9 | const observer = new MutationObserver((mutations) => { 10 | if (document.getElementById(id)) { 11 | resolve(document.getElementById(id)); 12 | observer.disconnect(); 13 | } 14 | }); 15 | 16 | observer.observe(document.body, { 17 | childList: true, 18 | subtree: true, 19 | }); 20 | return null; 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/hooks/useGoResourcePage.ts: -------------------------------------------------------------------------------- 1 | import { useHistory } from "react-router-dom"; 2 | import { waitForElement } from "../utils"; 3 | 4 | export function useGoResourcePage() { 5 | const history = useHistory(); 6 | 7 | return (id: number, uid: string, title?: string) => { 8 | if (id === 233) { 9 | history.push(`/discuss#${uid}`); 10 | } else { 11 | history.push({ 12 | pathname: "/resource", 13 | search: `?id=${id}`, 14 | state: { title: title || "资源页" }, 15 | }); 16 | } 17 | // jump to element 18 | waitForElement(uid).then((elm) => { 19 | document.getElementById(uid)?.scrollIntoView({ behavior: "smooth" }); 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/layout/frame/AppFrame.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { createStyles, makeStyles } from "@material-ui/core"; 3 | 4 | import { Header } from "./Header"; 5 | import { Footer } from "./Footer"; 6 | 7 | const useStyles = makeStyles(() => 8 | createStyles({ 9 | root: { 10 | flex: "1 0 100%", 11 | }, 12 | }) 13 | ); 14 | 15 | interface LayoutPropTypes { 16 | children: React.ReactNode; 17 | } 18 | 19 | export const AppFrame = (props: LayoutPropTypes) => { 20 | const { children } = props; 21 | 22 | const classes = useStyles(); 23 | 24 | return ( 25 | <> 26 |
27 |
{children}
28 |