Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
/node_modules
/.pnp
.pnp.js
package-lock.json
yarn.lock

# testing
/coverage
Expand Down Expand Up @@ -33,10 +35,7 @@ yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
.env*local

# vercel
.vercel
Expand Down
4 changes: 4 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
public-hoist-pattern[] = *import-in-the-middle*
public-hoist-pattern[] = *require-in-the-middle*
auto-install-peers = false

//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
@idea2app:registry=https://npm.pkg.github.com
always-auth=true
2 changes: 1 addition & 1 deletion components/Git/Issue/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const IssueCard: FC<IssueCardProps> = ({
)}
</div>

<article dangerouslySetInnerHTML={{ __html: marked(body || '') }} />
<article dangerouslySetInnerHTML={{ __html: marked(body || '', { async: false }) }} />

<footer className="flex items-center justify-between">
{user && (
Expand Down
2 changes: 1 addition & 1 deletion components/Member/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const MemberCard: FC<MemberCardProps> = observer(
</ul>

<p
dangerouslySetInnerHTML={{ __html: marked((summary as string) || '') }}
dangerouslySetInnerHTML={{ __html: marked((summary as string) || '', { async: false }) }}
className="text-neutral-500"
/>
</li>
Expand Down
72 changes: 72 additions & 0 deletions components/User/SessionBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { User } from '@idea2app/data-server';
import { Drawer, List, ListItem, ListItemButton, ListItemText } from '@mui/material';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import Link from 'next/link';
import { JWTProps } from 'next-ssr-middleware';
import { Component, HTMLAttributes, JSX } from 'react';

import { PageHead } from '../PageHead';
import { SessionForm } from './SessionForm';

export type MenuItem = Pick<JSX.IntrinsicElements['a'], 'href' | 'title'>;

export interface SessionBoxProps extends HTMLAttributes<HTMLDivElement>, JWTProps<User> {
path?: string;
menu?: MenuItem[];
}

@observer
export class SessionBox extends Component<SessionBoxProps> {
@observable
accessor modalShown = false;

componentDidMount() {
this.modalShown = !this.props.jwtPayload;
}

render() {
const { className = '', title, children, path, menu = [], jwtPayload, ...props } = this.props;

return (
<div className={`flex ${className}`} {...props}>
<div>
<List
component="nav"
className="sticky-top flex-col px-3"
style={{ top: '5rem', minWidth: '200px' }}
>
{menu.map(({ href, title }) => (
<ListItem key={href} disablePadding>
<ListItemButton
component={Link}
href={href || '#'}
selected={path?.split('?')[0].startsWith(href || '')}
className="rounded"
>
<ListItemText primary={title} />
</ListItemButton>
</ListItem>
))}
</List>
</div>
<main className="flex-1 pb-3">
<PageHead title={title} />

<h1 className="mb-4 text-3xl font-bold">{title}</h1>

{children}

<Drawer
anchor="right"
slotProps={{ paper: { className: 'p-4', style: { width: '400px' } } }}
open={this.modalShown}
onClose={() => (this.modalShown = false)}
>
<SessionForm onSignIn={() => window.location.reload()} />
</Drawer>
</main>
</div>
);
}
}
132 changes: 132 additions & 0 deletions components/User/SessionForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { PhoneSignInData } from '@idea2app/data-server';
import { Button, IconButton, InputAdornment, Tab, Tabs, TextField } from '@mui/material';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import { ObservedComponent } from 'mobx-react-helper';
import { FormEvent, MouseEvent } from 'react';
import { formToJSON } from 'web-utility';

import { i18n, I18nContext } from '../../models/Translation';
import userStore from '../../models/User';
import { SymbolIcon } from '../Icon';

export interface SessionFormProps {
onSignIn?: (data?: PhoneSignInData) => any;
}

@observer
export class SessionForm extends ObservedComponent<SessionFormProps, typeof i18n> {
static contextType = I18nContext;

@observable
accessor signType: 'up' | 'in' = 'in';

handleWebAuthn = async (event: MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();

const { t } = this.observedContext;

if (this.signType === 'up') {
const { mobilePhone } = formToJSON<PhoneSignInData>(event.currentTarget.form!);

if (!mobilePhone) throw new Error(t('phone_required_for_webauthn'));

await userStore.signUpWebAuthn(mobilePhone);
} else {
await userStore.signInWebAuthn();
}
this.props.onSignIn?.();
};

handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
event.stopPropagation();

const { t } = this.observedContext;
const { mobilePhone, password } = formToJSON<PhoneSignInData>(event.currentTarget);

if (this.signType === 'up') {
await userStore.signUp(mobilePhone, password);

this.signType = 'in';

alert(t('registration_success_please_login'));
} else {
await userStore.signIn(mobilePhone, password);

this.props.onSignIn?.({ mobilePhone, password });
}
};

render() {
const { signType } = this,
loading = userStore.uploading > 0;

const { t } = this.observedContext;

return (
<form className="flex flex-col gap-4" onSubmit={this.handleSubmit}>
<Tabs
value={signType}
variant="fullWidth"
className="mb-4"
onChange={(_, newValue: 'up' | 'in') => (this.signType = newValue)}
>
<Tab label={t('register')} value="up" />
<Tab label={t('login')} value="in" />
</Tabs>

<TextField
name="mobilePhone"
type="tel"
required
fullWidth
variant="outlined"
label={t('phone_number')}
placeholder={t('please_enter_phone')}
slotProps={{
htmlInput: {
pattern: '1[3-9]\\d{9}',
title: t('please_enter_correct_phone'),
},
input: {
startAdornment: <InputAdornment position="start">+86</InputAdornment>,
},
}}
/>
<div className="flex items-center gap-2">
<TextField
name="password"
type="password"
required
fullWidth
variant="outlined"
label={t('password')}
placeholder={t('please_enter_password')}
/>

<IconButton
size="large"
className="mb-2 self-end"
disabled={loading}
onClick={this.handleWebAuthn}
>
<SymbolIcon name="fingerprint" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fingerprint 没有在 html link 处引入,不会生效的

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fingerprint 没有在 html link 处引入,不会生效的

好的,没注意这么细节的问题,主要是先想让 AI 把通用的登录框代码移植过来,马上要做的下个 PR 我修复一下。

</IconButton>
</div>

<Button
className="mt-4"
type="submit"
variant="contained"
fullWidth
size="large"
disabled={loading}
>
{signType === 'up' ? t('register') : t('login')}
</Button>
</form>
);
}
}
31 changes: 28 additions & 3 deletions models/Base.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Base, ListChunk } from '@idea2app/data-server';
import { HTTPClient } from 'koajax';
import MIME from 'mime';
import { githubClient } from 'mobx-github';
import { TableCellValue, TableCellMedia, TableCellAttachment } from 'mobx-lark';
import { Filter, ListModel, toggle, IDType } from 'mobx-restful';
import { buildURLData } from 'web-utility';

import { API_Host, GITHUB_TOKEN, isServer } from './configuration';
import { Own_API_Host, GITHUB_TOKEN, isServer } from './configuration';

if (!isServer()) githubClient.baseURI = `${API_Host}/api/GitHub/`;
if (!isServer()) githubClient.baseURI = `${Own_API_Host}/api/GitHub/`;

githubClient.use(({ request }, next) => {
if (GITHUB_TOKEN)
Expand All @@ -19,7 +22,7 @@ githubClient.use(({ request }, next) => {
export { githubClient };

export const larkClient = new HTTPClient({
baseURI: `${API_Host}/api/Lark/`,
baseURI: `${Own_API_Host}/api/Lark/`,
responseType: 'json',
});

Expand All @@ -34,3 +37,25 @@ export function fileURLOf(field: TableCellValue, cache = false) {

return URI;
}

export abstract class TableModel<D extends Base, F extends Filter<D> = Filter<D>> extends ListModel<
D,
F
> {
@toggle('uploading')
async updateOne(data: Filter<D>, id?: IDType) {
const { body } = await (id
? this.client.put<D>(`${this.baseURI}/${id}`, data)
: this.client.post<D>(this.baseURI, data));

return (this.currentOne = body!);
}

async loadPage(pageIndex: number, pageSize: number, filter: F) {
const { body } = await this.client.get<ListChunk<D>>(
`${this.baseURI}?${buildURLData({ ...filter, pageIndex, pageSize })}`,
);

return { pageData: body!.list, totalCount: body!.count };
}
}
Loading