diff --git a/package.json b/package.json index 1d51854a3..34473658c 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "kami-design": "workspace:@mx-space/kami-design@*", "@floating-ui/react-dom": "1.2.2", "@formkit/auto-animate": "1.0.0-beta.6", - "@mx-space/api-client": "1.2.0", + "@mx-space/api-client": "1.3.2", "axios": "0.27.2", "clsx": "1.2.1", "css-spring": "4.1.0", diff --git a/packages/kami-design/components/Icons/for-footer.tsx b/packages/kami-design/components/Icons/for-footer.tsx index 77414e54c..6ac0fe8f8 100644 --- a/packages/kami-design/components/Icons/for-footer.tsx +++ b/packages/kami-design/components/Icons/for-footer.tsx @@ -18,3 +18,19 @@ export function FaSolidHeadphonesAlt(props: SVGProps) { ) } +export function SubscribeOutlined(props: SVGProps) { + return ( + + + + ) +} diff --git a/packages/kami-design/components/Modal/Modal.tsx b/packages/kami-design/components/Modal/Modal.tsx index a66592346..84b4586c2 100644 --- a/packages/kami-design/components/Modal/Modal.tsx +++ b/packages/kami-design/components/Modal/Modal.tsx @@ -49,6 +49,7 @@ export const Modal = forwardRef< resolve(null as any) props.disposer() }, 300) + props.onClose && props.onClose() }) }, [props.disposer]) diff --git a/packages/kami-design/components/Modal/stack-context.tsx b/packages/kami-design/components/Modal/stack-context.tsx index b4cb05a06..717a4d640 100644 --- a/packages/kami-design/components/Modal/stack-context.tsx +++ b/packages/kami-design/components/Modal/stack-context.tsx @@ -1,18 +1,18 @@ import { clsx } from 'clsx' import uniqueId from 'lodash-es/uniqueId' -import { +import type { FC, FunctionComponentElement, ReactChildren, ReactElement, ReactNode, - useEffect, } from 'react' import React, { createContext, createElement, memo, useContext, + useEffect, useRef, useState, } from 'react' @@ -198,6 +198,7 @@ export const ModalStackProvider: FC<{ component: $modalElement, id, disposer, + useBottomDrawerInMobile, ...rest, }, ] @@ -260,6 +261,7 @@ export const ModalStackProvider: FC<{ overlayProps, useBottomDrawerInMobile = true, } = comp + const extraProps = extraModalPropsMap.get(id)! const onClose = () => { @@ -288,7 +290,7 @@ export const ModalStackProvider: FC<{ dismissFnMapRef.current.set(Component, onClose) return ( =6'} dev: false - /@mx-space/api-client/1.2.0: - resolution: {integrity: sha512-cSD3speKTWM04uXCg6/ynRcAOwLwwKX8E84d6M1HId9dUEBv7S6f0A2Vw2t86sq0nNr0m+53ifjoLVYSGFQQwg==} + /@mx-space/api-client/1.3.2: + resolution: {integrity: sha512-umLREQDoou3osc/QPahOC4Jr9x11KzUd6+J0KUVjD21T55Q82SBV1watGTuEkM0LPGYiJsuWrsDQrgMBq/LmVg==} engines: {pnpm: '>=6'} dev: false diff --git a/src/components/layouts/AppLayout.tsx b/src/components/layouts/AppLayout.tsx index fabc8834b..1c6a96078 100644 --- a/src/components/layouts/AppLayout.tsx +++ b/src/components/layouts/AppLayout.tsx @@ -4,7 +4,6 @@ import type { FC } from 'react' import { useEffect, useInsertionEffect } from 'react' import type { AggregateRoot } from '@mx-space/api-client' -import { ModalStackProvider } from '@mx-space/kami-design/components/Modal/stack-context' import { MetaFooter } from '~/components/biz/Meta/footer' import { DynamicHeadMeta } from '~/components/biz/Meta/head' @@ -22,7 +21,7 @@ import { loadStyleSheet } from '~/utils/load-script' import { useStore } from '../../store' export const Content: FC = observer((props) => { - const { userStore: master, appUIStore } = useStore() + const { userStore: master } = useStore() useScreenMedia() const { check: checkBrowser } = useCheckOldBrowser() @@ -56,7 +55,7 @@ export const Content: FC = observer((props) => { }, []) return ( - + <> { - + ) }) diff --git a/src/components/layouts/BasicLayout/Footer/actions.tsx b/src/components/layouts/BasicLayout/Footer/actions.tsx index 6c6b99eaf..ced11bedc 100644 --- a/src/components/layouts/BasicLayout/Footer/actions.tsx +++ b/src/components/layouts/BasicLayout/Footer/actions.tsx @@ -13,6 +13,7 @@ import { import { RootPortal } from '@mx-space/kami-design/components/Portal' import { ScaleTransitionView } from '@mx-space/kami-design/components/Transition/scale' +import { SubscribeEmail } from '~/components/widgets/Subscribe' import { TrackerAction } from '~/constants/tracker' import { useAnalyze } from '~/hooks/use-analyze' import { useStore } from '~/store' @@ -101,7 +102,7 @@ export const FooterActions: FC = observer(() => { ) })} - + diff --git a/src/components/layouts/BasicLayout/index.tsx b/src/components/layouts/BasicLayout/index.tsx index 8a9b9242b..5085aeda7 100644 --- a/src/components/layouts/BasicLayout/index.tsx +++ b/src/components/layouts/BasicLayout/index.tsx @@ -10,6 +10,7 @@ import React, { import type { ShortcutOptions } from 'react-shortcut-guide' import { ShortcutProvider } from 'react-shortcut-guide' +import { ModalStackProvider } from '@mx-space/kami-design' import { BiMoonStarsFill, PhSunBold, @@ -113,7 +114,7 @@ export const BasicLayout: FC = observer(({ children }) => { }, []) const { event } = useAnalyze() return ( - <> +
@@ -152,6 +153,6 @@ export const BasicLayout: FC = observer(({ children }) => { - + ) }) diff --git a/src/components/widgets/Subscribe/index.tsx b/src/components/widgets/Subscribe/index.tsx new file mode 100644 index 000000000..00dd9d68f --- /dev/null +++ b/src/components/widgets/Subscribe/index.tsx @@ -0,0 +1,57 @@ +import { useEffect, useState } from 'react' + +import { SubscribeOutlined } from '@mx-space/kami-design/components/Icons' +import { useModalStack } from '@mx-space/kami-design/components/Modal' + +import { TrackerAction } from '~/constants/tracker' +import { useAnalyze } from '~/hooks/use-analyze' + +import { SubscribeModal } from './modal' +import { useSubscribeStatus } from './query' + +export const SubscribeEmail = () => { + const { event } = useAnalyze() + const { present } = useModalStack() + + const handleSubscribe = () => { + event({ + action: TrackerAction.Click, + label: `底部订阅点击`, + }) + const dispose = present({ + modalProps: { + title: '邮件订阅', + closeable: true, + useRootPortal: true, + }, + overlayProps: { + stopPropagation: true, + darkness: 0.5, + }, + component: () => , + useBottomDrawerInMobile: false, + }) + } + + const [canSubscribe, setCanSubscribe] = useState(false) + + const query = useSubscribeStatus() + + useEffect(() => { + const status = query.data?.enable + if (typeof status !== 'boolean') return + setCanSubscribe(status) + }, [query.data]) + + if (!canSubscribe) return null + + return ( + + ) +} diff --git a/src/components/widgets/Subscribe/modal.tsx b/src/components/widgets/Subscribe/modal.tsx new file mode 100644 index 000000000..0e1d7a683 --- /dev/null +++ b/src/components/widgets/Subscribe/modal.tsx @@ -0,0 +1,122 @@ +import { observer } from 'mobx-react-lite' +import { useReducer } from 'react' +import { message } from 'react-message-popup' + +import { Input } from '@mx-space/kami-design' +import { MdiEmailFastOutline } from '@mx-space/kami-design/components/Icons' + +import { apiClient } from '~/utils/client' + +import { useSubscribeStatus } from './query' + +interface SubscribeModalProps { + onConfirm: () => void +} + +const subscibeTextMap: Record = { + post_c: '文章', + note_c: '笔记', + say_c: '说说', + recently_c: '速记', +} + +const initialState = { + email: '', + types: { + post_c: false, + note_c: false, + say_c: false, + recently_c: false, + }, +} + +type Action = + | { type: 'set'; data: Partial } + | { type: 'reset' } + +const useFormData = () => { + const [state, dispatch] = useReducer( + (state: typeof initialState, payload: Action) => { + switch (payload.type) { + case 'set': + return { ...state, ...payload.data } + case 'reset': + return initialState + } + }, + { ...initialState }, + ) + return [state, dispatch] as const +} + +export const SubscribeModal = observer(({ onConfirm }) => { + const [state, dispatch] = useFormData() + + const query = useSubscribeStatus() + + const handleSubList = async () => { + if (!state.email) { + message.error('请输入邮箱') + return + } + if (Object.values(state.types).every((type) => !type)) { + message.error('请选择订阅类型') + return + } + const { email, types } = state + await apiClient.subscribe.subscribe( + email, + Object.keys(types).filter((name) => state.types[name]) as any[], + ) + + message.success('订阅成功') + dispatch({ type: 'reset' }) + onConfirm() + } + + return ( +
+ } + value={state.email} + onChange={(e) => { + dispatch({ type: 'set', data: { email: e.target.value } }) + }} + /> +
+ {Object.keys(state.types) + .filter((type) => query.data?.allowTypes.includes(type as any)) + .map((name) => ( +
+ { + dispatch({ + type: 'set', + data: { + types: { + ...state.types, + [name]: e.target.checked, + }, + }, + }) + }} + checked={state.types[name]} + id={name} + /> + +
+ ))} +
+ +
+ ) +}) diff --git a/src/components/widgets/Subscribe/query.ts b/src/components/widgets/Subscribe/query.ts new file mode 100644 index 000000000..a2a03af1b --- /dev/null +++ b/src/components/widgets/Subscribe/query.ts @@ -0,0 +1,12 @@ +import useSWR from 'swr' + +import { apiClient } from '~/utils/client' + +const SWR_CHECK_SUBSCRIBE_KEY = 'subscribe-status' + +export const useSubscribeStatus = () => { + return useSWR(SWR_CHECK_SUBSCRIBE_KEY, async () => { + const result = await apiClient.subscribe.check() + return result + }) +} diff --git a/tsconfig.json b/tsconfig.json index b221796ee..c9136ef46 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,7 +31,7 @@ "incremental": true, "paths": { "@mx-space/kami-design": [ - "../packages/kami-design" + "../packages/kami-design/index.ts" ], "@mx-space/kami-design/*": [ "../packages/kami-design/*"