diff --git a/apps/nestjs-backend/src/features/auth/auth.controller.ts b/apps/nestjs-backend/src/features/auth/auth.controller.ts index 5c991b83a..e4147b61f 100644 --- a/apps/nestjs-backend/src/features/auth/auth.controller.ts +++ b/apps/nestjs-backend/src/features/auth/auth.controller.ts @@ -47,7 +47,9 @@ export class AuthController { @Res({ passthrough: true }) res: Response, @Req() req: Express.Request ) { - const user = pickUserMe(await this.authService.signup(body.email, body.password)); + const user = pickUserMe( + await this.authService.signup(body.email, body.password, body.defaultSpaceName) + ); // set cookie, passport login await new Promise((resolve, reject) => { req.login(user, (err) => (err ? reject(err) : resolve())); diff --git a/apps/nestjs-backend/src/features/auth/auth.service.ts b/apps/nestjs-backend/src/features/auth/auth.service.ts index ed00f2ea4..31a3048e5 100644 --- a/apps/nestjs-backend/src/features/auth/auth.service.ts +++ b/apps/nestjs-backend/src/features/auth/auth.service.ts @@ -66,7 +66,7 @@ export class AuthService { return (await this.comparePassword(pass, password, salt)) ? { ...result, password } : null; } - async signup(email: string, password: string) { + async signup(email: string, password: string, defaultSpaceName?: string) { const user = await this.userService.getUserByEmail(email); if (user && (user.password !== null || user.accounts.length > 0)) { throw new HttpException(`User ${email} is already registered`, HttpStatus.BAD_REQUEST); @@ -83,14 +83,18 @@ export class AuthService { }, }); } - return await this.userService.createUserWithSettingCheck({ - id: generateUserId(), - name: email.split('@')[0], - email, - salt, - password: hashPassword, - lastSignTime: new Date().toISOString(), - }); + return await this.userService.createUserWithSettingCheck( + { + id: generateUserId(), + name: email.split('@')[0], + email, + salt, + password: hashPassword, + lastSignTime: new Date().toISOString(), + }, + undefined, + defaultSpaceName + ); }); } diff --git a/apps/nestjs-backend/src/features/user/user.service.ts b/apps/nestjs-backend/src/features/user/user.service.ts index d8280575f..50d06c6de 100644 --- a/apps/nestjs-backend/src/features/user/user.service.ts +++ b/apps/nestjs-backend/src/features/user/user.service.ts @@ -83,7 +83,8 @@ export class UserService { async createUserWithSettingCheck( user: Omit & { name?: string }, - account?: Omit + account?: Omit, + defaultSpaceName?: string ) { const setting = await this.prismaService.setting.findFirst({ select: { @@ -95,12 +96,13 @@ export class UserService { throw new BadRequestException('The current instance disallow sign up by the administrator'); } - return await this.createUser(user, account); + return await this.createUser(user, account, defaultSpaceName); } async createUser( user: Omit & { name?: string }, - account?: Omit + account?: Omit, + defaultSpaceName?: string ) { // defaults const defaultNotifyMeta: IUserNotifyMeta = { @@ -141,7 +143,7 @@ export class UserService { } await this.cls.runWith(this.cls.get(), async () => { this.cls.set('user.id', id); - await this.createSpaceBySignup({ name: `${name}'s space` }); + await this.createSpaceBySignup({ name: defaultSpaceName || `${name}'s space` }); }); this.eventEmitterService.emitAsync(Events.USER_SIGNUP, new UserSignUpEvent(id)); return newUser; diff --git a/apps/nextjs-app/src/features/app/blocks/import-table/TableImport.tsx b/apps/nextjs-app/src/features/app/blocks/import-table/TableImport.tsx index 39b146688..539d0bbed 100644 --- a/apps/nextjs-app/src/features/app/blocks/import-table/TableImport.tsx +++ b/apps/nextjs-app/src/features/app/blocks/import-table/TableImport.tsx @@ -15,7 +15,7 @@ import { importTableFromFile, inplaceImportTableFromFile, } from '@teable/openapi'; -import { useBase } from '@teable/sdk'; +import { useBase, LocalStorageKeys } from '@teable/sdk'; import { Dialog, DialogContent, @@ -35,11 +35,14 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, + AlertDialogTrigger, + Checkbox, } from '@teable/ui-lib'; import { toast } from '@teable/ui-lib/shadcn/ui/sonner'; import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; import { useState, useRef, useCallback } from 'react'; +import { useLocalStorage } from 'react-use'; import { FieldConfigPanel, InplaceFieldConfigPanel } from './field-config-panel'; import { UploadPanel } from './upload-panel'; import { UrlPanel } from './UrlPanel'; @@ -68,7 +71,6 @@ export const TableImport = (props: ITableImportProps) => { const [step, setStep] = useState(Step.UPLOAD); const { children, open, onOpenChange, fileType, tableId } = props; const [errorMessage, setErrorMessage] = useState(''); - const [alterDialogVisible, setAlterDialogVisible] = useState(false); const [file, setFile] = useState(null); const [fileInfo, setFileInfo] = useState({} as IAnalyzeRo); const primitiveWorkSheets = useRef({}); @@ -78,10 +80,8 @@ export const TableImport = (props: ITableImportProps) => { sourceWorkSheetKey: '', sourceColumnMap: {}, }); - - const closeDialog = () => { - dialogOpenProxy(false); - }; + const [shouldAlert, setShouldAlert] = useLocalStorage(LocalStorageKeys.ImportAlert, true); + const [shouldTips, setShouldTips] = useState(false); const { mutateAsync: importNewTableFn, isLoading } = useMutation({ mutationFn: async ({ baseId, importRo }: { baseId: string; importRo: IImportOptionRo }) => { @@ -241,17 +241,6 @@ export const TableImport = (props: ITableImportProps) => { [fileType, t] ); - const dialogOpenProxy = useCallback( - (open: boolean) => { - if (!open && Step.CONFIG && isLoading) { - setAlterDialogVisible(true); - return; - } - onOpenChange?.(open); - }, - [isLoading, onOpenChange] - ); - const fieldChangeHandler = (value: IImportOptionRo['worksheets']) => { setWorkSheets(value); }; @@ -262,7 +251,7 @@ export const TableImport = (props: ITableImportProps) => { return ( <> - + onOpenChange?.(open)}> {children && {children}} {open && ( { {step === Step.CONFIG && (
- - + + {shouldAlert ? ( + + + + ) : ( + + )} + + + {t('table:import.title.tipsTitle')} + + {t('table:import.tips.importAlert')} + + +
+ { + setShouldTips(res); + }} + /> + +
+ + {t('table:import.menu.cancel')} + { + importTable(); + if (shouldTips) { + setShouldAlert(false); + } + }} + > + {t('table:import.title.confirm')} + + +
+
)}
)}
- - setAlterDialogVisible(open)} - > - - - {t('table:import.title.leaveTitle')} - {t('table:import.tips.leaveTip')} - - - {t('table:import.menu.cancel')} - { - onOpenChange?.(false); - }} - > - {t('table:import.menu.leave')} - - - - ); }; diff --git a/apps/nextjs-app/src/features/app/blocks/import-table/field-config-panel/inplace-panel/FieldSelector.tsx b/apps/nextjs-app/src/features/app/blocks/import-table/field-config-panel/inplace-panel/FieldSelector.tsx index 7c772a8ac..bbc056038 100644 --- a/apps/nextjs-app/src/features/app/blocks/import-table/field-config-panel/inplace-panel/FieldSelector.tsx +++ b/apps/nextjs-app/src/features/app/blocks/import-table/field-config-panel/inplace-panel/FieldSelector.tsx @@ -30,7 +30,7 @@ interface IFieldSelector { export function FieldSelector(props: IFieldSelector) { const { options, onSelect, value, disabled = false } = props; const [open, setOpen] = useState(false); - const { t } = useTranslation(['table']); + const { t } = useTranslation(['table', 'common']); const comOptions = useMemo(() => { const result = [...options]; diff --git a/apps/nextjs-app/src/features/app/blocks/import-table/field-config-panel/new-create-panel/FieldConfigPanel.tsx b/apps/nextjs-app/src/features/app/blocks/import-table/field-config-panel/new-create-panel/FieldConfigPanel.tsx index 749a9f62b..938ef5aef 100644 --- a/apps/nextjs-app/src/features/app/blocks/import-table/field-config-panel/new-create-panel/FieldConfigPanel.tsx +++ b/apps/nextjs-app/src/features/app/blocks/import-table/field-config-panel/new-create-panel/FieldConfigPanel.tsx @@ -104,11 +104,12 @@ const FieldConfigPanel = (props: IFieldConfigPanel) => { key={sheetKey} size="xs" onClick={() => setSelectedSheetKey(sheetKey)} - className={cn('w-20 shrink-0 cursor-pointer truncate rounded-sm', { + className={cn('max-w-32 shrink-0 cursor-pointer truncate rounded-sm px-2', { 'bg-secondary': sheetKey === selectedSheetKey, })} + title={workSheets[sheetKey].name} > - {workSheets[sheetKey].name} + {workSheets[sheetKey].name} ))} diff --git a/apps/nextjs-app/src/features/app/blocks/space/SpaceCard.tsx b/apps/nextjs-app/src/features/app/blocks/space/SpaceCard.tsx index 616f001c7..9d97d25fc 100644 --- a/apps/nextjs-app/src/features/app/blocks/space/SpaceCard.tsx +++ b/apps/nextjs-app/src/features/app/blocks/space/SpaceCard.tsx @@ -79,7 +79,7 @@ export const SpaceCard: FC = (props) => { onChange={(e) => setSpaceName(e.target.value)} onBlur={(e) => toggleUpdateSpace(e)} > - + {space.name} diff --git a/apps/nextjs-app/src/features/app/blocks/space/space-side-bar/SpaceList.tsx b/apps/nextjs-app/src/features/app/blocks/space/space-side-bar/SpaceList.tsx index cff54c6bf..d1781de39 100644 --- a/apps/nextjs-app/src/features/app/blocks/space/space-side-bar/SpaceList.tsx +++ b/apps/nextjs-app/src/features/app/blocks/space/space-side-bar/SpaceList.tsx @@ -14,7 +14,7 @@ import { SpaceItem } from './SpaceItem'; export const SpaceList: FC = () => { const router = useRouter(); const { disallowSpaceCreation } = useSetting(); - const { t } = useTranslation('space'); + const { t } = useTranslation('common'); const queryClient = useQueryClient(); const { data: spaceList } = useQuery({ @@ -46,7 +46,7 @@ export const SpaceList: FC = () => { className="w-full" onClick={() => { const name = getUniqName( - t('defaultSpaceName'), + t('noun.space'), spaceList?.data?.length ? spaceList?.data.map((space) => space?.name) : [] ); addSpace({ name }); diff --git a/apps/nextjs-app/src/features/app/components/notifications/NotificationActionBar.tsx b/apps/nextjs-app/src/features/app/components/notifications/NotificationActionBar.tsx index 32278e7fb..3e121542f 100644 --- a/apps/nextjs-app/src/features/app/components/notifications/NotificationActionBar.tsx +++ b/apps/nextjs-app/src/features/app/components/notifications/NotificationActionBar.tsx @@ -10,6 +10,7 @@ import { TooltipProvider, TooltipTrigger, } from '@teable/ui-lib'; +import { useTranslation } from 'next-i18next'; import React from 'react'; interface ActionBarProps { @@ -20,6 +21,7 @@ interface ActionBarProps { export const NotificationActionBar: React.FC = (props) => { const { notifyStatus, children, onStatusCheck } = props; + const { t } = useTranslation('common'); return ( @@ -45,8 +47,12 @@ export const NotificationActionBar: React.FC = (props) => { - Mark this notification as - {notifyStatus === NotificationStatesEnum.Unread ? ' read' : ' unread'} + {t('notification.markAs', { + status: + notifyStatus === NotificationStatesEnum.Unread + ? t('notification.read') + : t('notification.unread'), + })} @@ -60,7 +66,7 @@ export const NotificationActionBar: React.FC = (props) => { - Change page notification settings + {t('notification.changeSetting')} diff --git a/apps/nextjs-app/src/features/app/components/notifications/NotificationsManage.tsx b/apps/nextjs-app/src/features/app/components/notifications/NotificationsManage.tsx index e67d2c589..8d79b2311 100644 --- a/apps/nextjs-app/src/features/app/components/notifications/NotificationsManage.tsx +++ b/apps/nextjs-app/src/features/app/components/notifications/NotificationsManage.tsx @@ -10,12 +10,14 @@ import { useNotification } from '@teable/sdk'; import { ReactQueryKeys } from '@teable/sdk/config/react-query-keys'; import { Button, Popover, PopoverContent, PopoverTrigger } from '@teable/ui-lib'; import { cn } from '@teable/ui-lib/shadcn'; +import { useTranslation } from 'next-i18next'; import React, { useEffect, useState } from 'react'; import { NotificationList } from './NotificationList'; export const NotificationsManage: React.FC = () => { const queryClient = useQueryClient(); const notification = useNotification(); + const { t } = useTranslation('common'); const [isOpen, setOpen] = useState(false); const [unreadCount, setUnreadCount] = useState(0); @@ -83,7 +85,7 @@ export const NotificationsManage: React.FC = () => { }} > -

{num} new

+

{t('notification.new', { count: num })}

); @@ -133,14 +135,14 @@ export const NotificationsManage: React.FC = () => { }} > - Mark all as read + {t('notification.markAllAsRead')} ) : ( '' )}
-
Notifications
+
{t('notification.title')}
{renderNewButton()}
diff --git a/apps/nextjs-app/src/features/auth/components/SignForm.tsx b/apps/nextjs-app/src/features/auth/components/SignForm.tsx index 9347ff504..02c06172f 100644 --- a/apps/nextjs-app/src/features/auth/components/SignForm.tsx +++ b/apps/nextjs-app/src/features/auth/components/SignForm.tsx @@ -29,7 +29,10 @@ export const SignForm: FC = (props) => { return signin(form); } if (type === 'signup') { - return signup(form); + return signup({ + ...form, + defaultSpaceName: t('space:initialSpaceName', { name: form.email.split('@')[0] }), + }); } throw new Error('Invalid type'); }, diff --git a/apps/nextjs-app/src/features/i18n/auth.config.ts b/apps/nextjs-app/src/features/i18n/auth.config.ts index 988fc4072..f923c84e1 100644 --- a/apps/nextjs-app/src/features/i18n/auth.config.ts +++ b/apps/nextjs-app/src/features/i18n/auth.config.ts @@ -2,9 +2,9 @@ import type { I18nActiveNamespaces } from '@/lib/i18n'; export interface IAuthConfig { // Define namespaces in use in both the type and the config. - i18nNamespaces: I18nActiveNamespaces<'common' | 'auth'>; + i18nNamespaces: I18nActiveNamespaces<'common' | 'auth' | 'space'>; } export const authConfig: IAuthConfig = { - i18nNamespaces: ['common', 'auth'], + i18nNamespaces: ['common', 'auth', 'space'], }; diff --git a/packages/common-i18n/src/locales/en/common.json b/packages/common-i18n/src/locales/en/common.json index 0c41d9558..72e0af19c 100644 --- a/packages/common-i18n/src/locales/en/common.json +++ b/packages/common-i18n/src/locales/en/common.json @@ -218,5 +218,15 @@ "allowSpaceCreation": "Allow everyone to create new spaces", "allowSpaceCreationDescription": "Disabling this option will prevent users other than administrators from creating new spaces." } + }, + "notification": { + "title": "Notifications", + "unread": "Unread", + "read": "Read", + "markAs": "Mark this notification as {{status}}", + "markAllAsRead": "Mark all as read", + "noUnread": "No unread notifications", + "changeSetting": "Change page notification settings", + "new": "new {{count}}" } } diff --git a/packages/common-i18n/src/locales/en/space.json b/packages/common-i18n/src/locales/en/space.json index f80ca835d..ed76e09d3 100644 --- a/packages/common-i18n/src/locales/en/space.json +++ b/packages/common-i18n/src/locales/en/space.json @@ -1,5 +1,5 @@ { - "defaultSpaceName": "Space", + "initialSpaceName": "{{name}}'s space", "page": { "title": "Teable App" }, diff --git a/packages/common-i18n/src/locales/en/table.json b/packages/common-i18n/src/locales/en/table.json index 65ab664e8..e8c067e22 100644 --- a/packages/common-i18n/src/locales/en/table.json +++ b/packages/common-i18n/src/locales/en/table.json @@ -142,10 +142,11 @@ "importTitle": "Create a new table", "incrementImportTitle": "Import Into — ", "optionsTitle": "Import option", - "leaveTitle": "Are you sure to leave this page?", "primitiveFields": "Primitive Fields", "importFields": "Import Field", - "primaryField": "Primary Field" + "primaryField": "Primary Field", + "tipsTitle": "Tips", + "confirm": "Confirm and continue" }, "menu": { "addFromOtherSource": "Add from other sources", @@ -163,7 +164,9 @@ "analyzing": "analyzing", "notSupportFieldType": "Field type is not supported", "resultEmpty": "No results found.", - "searchPlaceholder": "Search import field" + "searchPlaceholder": "Search...", + "importAlert": "Once the import begins, it cannot be stopped until it is either successfully completed or terminated due to failure. The import result will be notify to you later. You'd better do not operate the table during the import, which may cause errors.", + "noTips": "I've know that, don't show again" }, "options": { "autoSelectFieldOptionName": "Auto-select field types", diff --git a/packages/common-i18n/src/locales/zh/common.json b/packages/common-i18n/src/locales/zh/common.json index 39a02fda2..74fdb6e16 100644 --- a/packages/common-i18n/src/locales/zh/common.json +++ b/packages/common-i18n/src/locales/zh/common.json @@ -217,5 +217,15 @@ "allowSpaceCreation": "允许所有人创建新的空间", "allowSpaceCreationDescription": "关闭此选项将禁止除管理员以外的用户创建新的空间。" } + }, + "notification": { + "title": "通知", + "unread": "未读", + "read": "已读", + "markAs": "将此通知标记为{{status}}", + "markAllAsRead": "全部标记为已读", + "noUnread": "没有未读通知", + "changeSetting": "更改页面通知设置", + "new": "新消息{{count}}条" } } diff --git a/packages/common-i18n/src/locales/zh/space.json b/packages/common-i18n/src/locales/zh/space.json index 3bd554106..a8b7ddac5 100644 --- a/packages/common-i18n/src/locales/zh/space.json +++ b/packages/common-i18n/src/locales/zh/space.json @@ -1,5 +1,5 @@ { - "defaultSpaceName": "空间", + "initialSpaceName": "{{name}}的空间", "page": { "title": "Teable App" }, diff --git a/packages/common-i18n/src/locales/zh/table.json b/packages/common-i18n/src/locales/zh/table.json index 20ff22a7f..a7c73e22d 100644 --- a/packages/common-i18n/src/locales/zh/table.json +++ b/packages/common-i18n/src/locales/zh/table.json @@ -142,10 +142,11 @@ "importTitle": "创建一个新表格", "incrementImportTitle": "导入到 — ", "optionsTitle": "导入选项", - "leaveTitle": "确定离开此页面?", "primitiveFields": "原表字段", "importFields": "导入的字段", - "primaryField": "主键" + "primaryField": "主键", + "tipsTitle": "温馨提示", + "confirm": "确认并继续" }, "menu": { "addFromOtherSource": "从第三方资源导入", @@ -162,7 +163,10 @@ "fileExceedSizeTip": "该类型文件限制文件大小为", "analyzing": "分析中", "notSupportFieldType": "字段类型不支持", - "resultEmpty": "未查询到结果" + "resultEmpty": "未查询到结果", + "searchPlaceholder": "搜索...", + "importAlert": "一旦导入,不能中止,直到导入成功或失败中止,导入结果将会在推送到通知栏。导入期间,最好不要操作表格,可能会引起错误。", + "noTips": "我已知晓,不再提示" }, "options": { "autoSelectFieldOptionName": "自动预测类型", diff --git a/packages/openapi/src/auth/signup.ts b/packages/openapi/src/auth/signup.ts index 27317af17..05cf2fe86 100644 --- a/packages/openapi/src/auth/signup.ts +++ b/packages/openapi/src/auth/signup.ts @@ -6,7 +6,9 @@ import { signinSchema, signinVoSchema } from './signin'; export const SIGN_UP = '/auth/signup'; -export const signupSchema = signinSchema; +export const signupSchema = signinSchema.extend({ + defaultSpaceName: z.string().optional(), +}); export type ISignup = z.infer; diff --git a/packages/sdk/src/components/group/Group.tsx b/packages/sdk/src/components/group/Group.tsx index d8e1ed0f3..0bb34fa69 100644 --- a/packages/sdk/src/components/group/Group.tsx +++ b/packages/sdk/src/components/group/Group.tsx @@ -24,7 +24,7 @@ export const Group = (props: IGroupProps) => { : t('group.label'); return { text, - isActive: text !== 'Group', + isActive: text !== t('group.label'), Icon: LayoutList, }; }, [groupLength, t]); diff --git a/packages/sdk/src/components/sort/SortItem.tsx b/packages/sdk/src/components/sort/SortItem.tsx index 118e8f325..2d43284aa 100644 --- a/packages/sdk/src/components/sort/SortItem.tsx +++ b/packages/sdk/src/components/sort/SortItem.tsx @@ -29,6 +29,7 @@ function SortItem(props: ISortItemProps) { value={fieldId} onSelect={(value) => selectHandler(ISortKey.FieldId, value)} excludedIds={selectedFields} + className="w-40" {...restProps} /> diff --git a/packages/sdk/src/config/local-storage-keys.ts b/packages/sdk/src/config/local-storage-keys.ts index c34c1054a..f9d646ae4 100644 --- a/packages/sdk/src/config/local-storage-keys.ts +++ b/packages/sdk/src/config/local-storage-keys.ts @@ -9,4 +9,5 @@ export enum LocalStorageKeys { ViewGridCollapsedGroup = 'ls_view_grid_collapsed_group', ViewKanbanCollapsedStack = 'ls_view_kanban_collapsed_stack', CompletedGuideMap = 'ls_completed_guide_map', + ImportAlert = 'ls_import_alert', }