Skip to content
Snippets Groups Projects
Unverified Commit 3335bcc7 authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Migrate setup to RTK (#31002)

parent 14aa5a07
No related branches found
No related tags found
No related merge requests found
Showing
with 343 additions and 249 deletions
......@@ -8,6 +8,7 @@ export * from "./dashboard";
export * from "./database";
export * from "./dataset";
export * from "./field";
export * from "./group";
export * from "./metric";
export * from "./models";
export * from "./modelIndexes";
......
......@@ -8,7 +8,7 @@ import { PLUGIN_REDUCERS } from "metabase/plugins";
import admin from "metabase/admin/admin";
/* setup */
import * as setup from "metabase/setup/reducers";
import { reducer as setup } from "metabase/setup/reducers";
/* dashboards */
import dashboard from "metabase/dashboard/reducers";
......@@ -48,7 +48,7 @@ export default {
qb: combineReducers(qb),
reference,
revisions,
setup: combineReducers(setup),
setup,
admin,
plugins: combineReducers(PLUGIN_REDUCERS),
};
......@@ -37,7 +37,7 @@ import CollectionPermissionsModal from "metabase/admin/permissions/components/Co
import UserCollectionList from "metabase/containers/UserCollectionList";
import PulseEditApp from "metabase/pulse/containers/PulseEditApp";
import SetupApp from "metabase/setup/containers/SetupApp";
import { Setup } from "metabase/setup/components/Setup";
import NewModelOptions from "metabase/models/containers/NewModelOptions";
......@@ -98,7 +98,7 @@ export const getRoutes = store => (
{/* SETUP */}
<Route
path="/setup"
component={SetupApp}
component={Setup}
onEnter={(nextState, replace) => {
if (MetabaseSettings.hasUserSetup()) {
replace("/");
......
import { createAction } from "redux-actions";
import { getIn } from "icepick";
import { SetupApi, UtilApi } from "metabase/services";
import { createThunkAction } from "metabase/lib/redux";
import { loadLocalization } from "metabase/lib/i18n";
import { createAction, createAsyncThunk } from "@reduxjs/toolkit";
import { SetupApi } from "metabase/services";
import MetabaseSettings from "metabase/lib/settings";
import { loadLocalization } from "metabase/lib/i18n";
import { DatabaseData } from "metabase-types/api";
import { Locale } from "metabase-types/store";
import { getUserToken, getDefaultLocale, getLocales } from "./utils";
export const SET_STEP = "metabase/setup/SET_STEP";
export const setStep = createAction(SET_STEP);
export const SET_LOCALE = "metabase/setup/SET_LOCALE";
export const SET_LOCALE_LOADED = "metabase/setup/SET_LOCALE_LOADED";
export const setLocale = createThunkAction(
SET_LOCALE_LOADED,
(locale: Locale) => async (dispatch: any) => {
dispatch({ type: SET_LOCALE, payload: locale });
await loadLocalization(locale.code);
},
);
export const SET_USER = "metabase/setup/SET_USER";
export const setUser = createAction(SET_USER);
export const SET_DATABASE_ENGINE = "metabase/setup/SET_DATABASE_ENGINE";
export const setDatabaseEngine = createAction(SET_DATABASE_ENGINE);
export const SET_DATABASE = "metabase/setup/SET_DATABASE";
export const setDatabase = createAction(SET_DATABASE);
export const SET_INVITE = "metabase/setup/SET_INVITE";
export const setInvite = createAction(SET_INVITE);
export const SET_TRACKING = "metabase/setup/SET_TRACKING";
export const setTracking = createAction(SET_TRACKING);
import { InviteInfo, Locale, State, UserInfo } from "metabase-types/store";
import {
trackAddDataLaterClicked,
trackDatabaseSelected,
trackDatabaseStepCompleted,
trackTrackingChanged,
trackUserStepCompleted,
trackWelcomeStepCompleted,
} from "./analytics";
import {
getAvailableLocales,
getDatabase,
getInvite,
getIsTrackingAllowed,
getLocale,
getSetupToken,
getUser,
} from "./selectors";
import { getDefaultLocale, getLocales, getUserToken } from "./utils";
interface ThunkConfig {
state: State;
}
export const LOAD_USER_DEFAULTS = "metabase/setup/LOAD_USER_DEFAULTS";
export const loadUserDefaults = createThunkAction(
export const loadUserDefaults = createAsyncThunk(
LOAD_USER_DEFAULTS,
() => async (dispatch: any) => {
async (): Promise<UserInfo | undefined> => {
const token = getUserToken();
if (token) {
const defaults = await SetupApi.user_defaults({ token });
dispatch(setUser(defaults.user));
return defaults.user;
}
},
);
export const LOAD_LOCALE_DEFAULTS = "metabase/setup/LOAD_LOCALE_DEFAULTS";
export const loadLocaleDefaults = createThunkAction(
LOAD_LOCALE_DEFAULTS,
() => async (dispatch: any) => {
const data = MetabaseSettings.get("available-locales") || [];
const locale = getDefaultLocale(getLocales(data));
await dispatch(setLocale(locale));
export const loadLocaleDefaults = createAsyncThunk<
Locale | undefined,
void,
ThunkConfig
>(LOAD_LOCALE_DEFAULTS, async (_, { getState }) => {
const data = getAvailableLocales(getState());
const locale = getDefaultLocale(getLocales(data));
if (locale) {
await loadLocalization(locale.code);
}
return locale;
});
export const LOAD_DEFAULTS = "metabase/setup/LOAD_DEFAULTS";
export const loadDefaults = createAsyncThunk<void, void, ThunkConfig>(
LOAD_DEFAULTS,
(_, { dispatch }) => {
dispatch(loadUserDefaults());
dispatch(loadLocaleDefaults());
},
);
export const validatePassword = async (password: string) => {
const error = MetabaseSettings.passwordComplexityDescription(password);
if (error) {
return error;
}
export const SELECT_STEP = "metabase/setup/SUBMIT_WELCOME_STEP";
export const selectStep = createAction<number>(SELECT_STEP);
try {
await UtilApi.password_check({ password });
} catch (error) {
return getIn(error, ["data", "errors", "password"]);
}
};
export const SUBMIT_WELCOME = "metabase/setup/SUBMIT_WELCOME_STEP";
export const submitWelcome = createAsyncThunk(SUBMIT_WELCOME, () => {
trackWelcomeStepCompleted();
});
export const UPDATE_LOCALE = "metabase/setup/UPDATE_LOCALE";
export const updateLocale = createAsyncThunk(
UPDATE_LOCALE,
async (locale: Locale) => {
await loadLocalization(locale.code);
},
);
export const SUBMIT_LANGUAGE = "metabase/setup/SUBMIT_LANGUAGE";
export const submitLanguage = createAction(SUBMIT_LANGUAGE);
export const submitUser = createAsyncThunk(
"metabase/setup/SUBMIT_USER_INFO",
(_: UserInfo) => {
trackUserStepCompleted();
},
);
export const VALIDATE_DATABASE = "metabase/setup/VALIDATE_DATABASE";
export const validateDatabase = createThunkAction(
VALIDATE_DATABASE,
(database: DatabaseData) => async () => {
await SetupApi.validate_db({
token: MetabaseSettings.get("setup-token"),
details: database,
});
export const UPDATE_DATABASE_ENGINE = "metabase/setup/UPDATE_DATABASE_ENGINE";
export const updateDatabaseEngine = createAsyncThunk(
UPDATE_DATABASE_ENGINE,
(engine?: string) => {
if (engine) {
trackDatabaseSelected(engine);
}
},
);
const validateDatabase = async (token: string, database: DatabaseData) => {
await SetupApi.validate_db({
token,
details: database,
});
};
export const SUBMIT_DATABASE = "metabase/setup/SUBMIT_DATABASE";
export const submitDatabase = createThunkAction(
export const submitDatabase = createAsyncThunk<
DatabaseData,
DatabaseData,
ThunkConfig
>(
SUBMIT_DATABASE,
(database: DatabaseData) => async (dispatch: any) => {
async (database: DatabaseData, { getState, rejectWithValue }) => {
const token = getSetupToken(getState());
const sslDetails = { ...database.details, ssl: true };
const sslDatabase = { ...database, details: sslDetails };
const nonSslDetails = { ...database.details, ssl: false };
const nonSslDatabase = { ...database, database: nonSslDetails };
if (!token) {
return database;
}
try {
await dispatch(validateDatabase(sslDatabase));
await dispatch(setDatabase(sslDatabase));
} catch (error) {
await dispatch(validateDatabase(nonSslDatabase));
await dispatch(setDatabase(nonSslDatabase));
await validateDatabase(token, sslDatabase);
trackDatabaseStepCompleted(database.engine);
return sslDatabase;
} catch (error1) {
try {
await validateDatabase(token, nonSslDatabase);
trackDatabaseStepCompleted(database.engine);
return nonSslDatabase;
} catch (error2) {
return rejectWithValue(error2);
}
}
},
);
export const SUBMIT_USER_INVITE = "metabase/setup/SUBMIT_USER_INVITE";
export const submitUserInvite = createAsyncThunk(
SUBMIT_USER_INVITE,
(_: InviteInfo) => {
trackDatabaseStepCompleted();
},
);
export const SKIP_DATABASE = "metabase/setup/SKIP_DATABASE";
export const skipDatabase = createAsyncThunk(
SKIP_DATABASE,
(engine?: string) => {
trackDatabaseStepCompleted();
trackAddDataLaterClicked(engine);
},
);
export const UPDATE_TRACKING = "metabase/setup/UPDATE_TRACKING";
export const updateTracking = createAsyncThunk(
UPDATE_TRACKING,
(isTrackingAllowed: boolean) => {
trackTrackingChanged(isTrackingAllowed);
MetabaseSettings.set("anon-tracking-enabled", isTrackingAllowed);
trackTrackingChanged(isTrackingAllowed);
},
);
export const SUBMIT_SETUP = "metabase/setup/SUBMIT_SETUP";
export const submitSetup = createThunkAction(
export const submitSetup = createAsyncThunk<void, void, ThunkConfig>(
SUBMIT_SETUP,
() => async (dispatch: any, getState: any) => {
const { setup } = getState();
const { locale, user, database, invite, isTrackingAllowed } = setup;
await SetupApi.create({
token: MetabaseSettings.get("setup-token"),
user,
database,
invite,
prefs: {
site_name: user.site_name,
site_locale: locale.code,
allow_tracking: isTrackingAllowed.toString(),
},
});
MetabaseSettings.set("setup-token", null);
async (_, { getState, rejectWithValue }) => {
const token = getSetupToken(getState());
const locale = getLocale(getState());
const user = getUser(getState());
const database = getDatabase(getState());
const invite = getInvite(getState());
const isTrackingAllowed = getIsTrackingAllowed(getState());
try {
await SetupApi.create({
token,
user,
database,
invite,
prefs: {
site_name: user?.site_name,
site_locale: locale?.code,
allow_tracking: isTrackingAllowed.toString(),
},
});
MetabaseSettings.set("setup-token", null);
} catch (error) {
return rejectWithValue(error);
}
},
);
......@@ -6,13 +6,13 @@ import {
StepLabelText,
} from "./ActiveStep.styled";
export interface ActiveStepProps {
interface ActiveStepProps {
title: string;
label: number;
children?: ReactNode;
}
const ActiveStep = ({
export const ActiveStep = ({
title,
label,
children,
......@@ -27,6 +27,3 @@ const ActiveStep = ({
</StepRoot>
);
};
// eslint-disable-next-line import/no-default-export -- deprecated usage
export default ActiveStep;
// eslint-disable-next-line import/no-default-export -- deprecated usage
export { default } from "./ActiveStep";
export * from "./ActiveStep";
import React from "react";
import { connect } from "react-redux";
import { t } from "ttag";
import HelpCard from "metabase/components/HelpCard";
import { SetupCardContainer } from "metabase/setup/components/SetupCardContainer";
import { useSelector } from "metabase/lib/redux";
import MetabaseSettings from "metabase/lib/settings";
import { getSetting } from "metabase/selectors/settings";
import type { State } from "metabase-types/store";
import HelpCard from "metabase/components/HelpCard";
import { COMPLETED_STEP } from "../../constants";
import { isStepActive } from "../../selectors";
const mapStateToProps = (state: State) => ({
isHosted: getSetting(state, "is-hosted?"),
isStepActive: isStepActive(state, COMPLETED_STEP),
});
interface CloudMigrationHelpProps {
isHosted: boolean;
isStepActive: boolean;
}
import { getIsHosted, getIsStepActive } from "../../selectors";
import { SetupCardContainer } from "../SetupCardContainer";
const CloudMigrationHelp = ({
isHosted,
isStepActive,
}: CloudMigrationHelpProps) => {
export const CloudMigrationHelp = () => {
const isHosted = useSelector(getIsHosted);
const isStepActive = useSelector(state =>
getIsStepActive(state, COMPLETED_STEP),
);
const isVisible = isHosted && isStepActive;
return (
......@@ -38,6 +23,3 @@ const CloudMigrationHelp = ({
</SetupCardContainer>
);
};
// eslint-disable-next-line import/no-default-export -- deprecated usage
export default connect(mapStateToProps)(CloudMigrationHelp);
export * from "./CloudMigrationHelp";
import React from "react";
import { t } from "ttag";
import NewsletterForm from "../../containers/NewsletterForm";
import { useSelector } from "metabase/lib/redux";
import { COMPLETED_STEP } from "../../constants";
import { getIsStepActive } from "../../selectors";
import { NewsletterForm } from "../NewsletterForm";
import {
StepBody,
StepFooter,
......@@ -8,13 +11,10 @@ import {
StepTitle,
} from "./CompletedStep.styled";
export interface CompletedStepProps {
isStepActive: boolean;
}
const CompletedStep = ({
isStepActive,
}: CompletedStepProps): JSX.Element | null => {
export const CompletedStep = (): JSX.Element | null => {
const isStepActive = useSelector(state =>
getIsStepActive(state, COMPLETED_STEP),
);
if (!isStepActive) {
return null;
}
......@@ -35,6 +35,3 @@ const CompletedStep = ({
</StepRoot>
);
};
// eslint-disable-next-line import/no-default-export -- deprecated usage
export default CompletedStep;
import React from "react";
import { render, screen } from "@testing-library/react";
import CompletedStep, { CompletedStepProps } from "./CompletedStep";
import {
createMockSetupState,
createMockState,
} from "metabase-types/store/mocks";
import { renderWithProviders, screen } from "__support__/ui";
import { COMPLETED_STEP, USER_STEP } from "../../constants";
import { CompletedStep } from "./CompletedStep";
interface SetupOpts {
step?: number;
}
const setup = ({ step = COMPLETED_STEP }: SetupOpts = {}) => {
const state = createMockState({
setup: createMockSetupState({
step,
}),
});
const NewsletterFormMock = () => <div>Metabase Newsletter</div>;
jest.mock("../../containers/NewsletterForm", () => NewsletterFormMock);
renderWithProviders(<CompletedStep />, { storeInitialState: state });
};
describe("CompletedStep", () => {
it("should render in inactive state", () => {
const props = getProps({
isStepActive: false,
});
render(<CompletedStep {...props} />);
setup({ step: USER_STEP });
expect(screen.queryByText("You're all set up!")).not.toBeInTheDocument();
});
it("should show a newsletter form and a link to the app", () => {
const props = getProps({
isStepActive: true,
});
render(<CompletedStep {...props} />);
setup({ step: COMPLETED_STEP });
expect(screen.getByText("Metabase Newsletter")).toBeInTheDocument();
expect(screen.getByText("Take me to Metabase")).toBeInTheDocument();
});
});
const getProps = (opts?: Partial<CompletedStepProps>): CompletedStepProps => ({
isStepActive: false,
...opts,
});
// eslint-disable-next-line import/no-default-export -- deprecated usage
export { default } from "./CompletedStep";
export * from "./CompletedStep";
import React from "react";
import { useSelector } from "metabase/lib/redux";
import DatabaseHelpCard from "metabase/databases/containers/DatabaseHelpCard";
import { DATABASE_STEP } from "../../constants";
import { getDatabaseEngine, getIsStepActive } from "../../selectors";
import { SetupCardContainer } from "../SetupCardContainer";
export interface DatabaseHelpProps {
engine?: string;
isStepActive: boolean;
}
const DatabaseHelp = ({
engine,
isStepActive,
}: DatabaseHelpProps): JSX.Element => {
export const DatabaseHelp = (): JSX.Element => {
const engine = useSelector(getDatabaseEngine);
const isStepActive = useSelector(state =>
getIsStepActive(state, DATABASE_STEP),
);
const isVisible = isStepActive && engine != null;
return (
......@@ -19,6 +18,3 @@ const DatabaseHelp = ({
</SetupCardContainer>
);
};
// eslint-disable-next-line import/no-default-export -- deprecated usage
export default DatabaseHelp;
// eslint-disable-next-line import/no-default-export -- deprecated usage
export { default } from "./DatabaseHelp";
export * from "./DatabaseHelp";
import React, { useCallback } from "react";
import React from "react";
import { t } from "ttag";
import { updateIn } from "icepick";
import { useDispatch, useSelector } from "metabase/lib/redux";
import DatabaseForm from "metabase/databases/containers/DatabaseForm";
import { DatabaseData } from "metabase-types/api";
import { InviteInfo, UserInfo } from "metabase-types/store";
import ActiveStep from "../ActiveStep";
import InactiveStep from "../InvactiveStep";
import InviteUserForm from "../InviteUserForm";
import SetupSection from "../SetupSection";
import { InviteInfo } from "metabase-types/store";
import {
selectStep,
skipDatabase,
submitDatabase,
submitUserInvite,
updateDatabaseEngine,
} from "../../actions";
import { DATABASE_STEP } from "../../constants";
import {
getDatabase,
getDatabaseEngine,
getInvite,
getIsEmailConfigured,
getIsSetupCompleted,
getIsStepActive,
getIsStepCompleted,
getUser,
} from "../../selectors";
import { ActiveStep } from "../ActiveStep";
import { InactiveStep } from "../InvactiveStep";
import { InviteUserForm } from "../InviteUserForm";
import { SetupSection } from "../SetupSection";
import { StepDescription } from "./DatabaseStep.styled";
export interface DatabaseStepProps {
user?: UserInfo;
database?: DatabaseData;
engine?: string;
invite?: InviteInfo;
isEmailConfigured: boolean;
isStepActive: boolean;
isStepCompleted: boolean;
isSetupCompleted: boolean;
onEngineChange: (engine?: string) => void;
onStepSelect: () => void;
onDatabaseSubmit: (database: DatabaseData) => void;
onInviteSubmit: (invite: InviteInfo) => void;
onStepCancel: (engine?: string) => void;
}
const DatabaseStep = ({
user,
database,
engine,
invite,
isEmailConfigured,
isStepActive,
isStepCompleted,
isSetupCompleted,
onEngineChange,
onStepSelect,
onDatabaseSubmit,
onInviteSubmit,
onStepCancel,
}: DatabaseStepProps): JSX.Element => {
const handleSubmit = useCallback(
async (database: DatabaseData) => {
try {
await onDatabaseSubmit(database);
} catch (error) {
throw getSubmitError(error);
}
},
[onDatabaseSubmit],
export const DatabaseStep = (): JSX.Element => {
const user = useSelector(getUser);
const database = useSelector(getDatabase);
const engine = useSelector(getDatabaseEngine);
const invite = useSelector(getInvite);
const isEmailConfigured = useSelector(getIsEmailConfigured);
const isStepActive = useSelector(state =>
getIsStepActive(state, DATABASE_STEP),
);
const isStepCompleted = useSelector(state =>
getIsStepCompleted(state, DATABASE_STEP),
);
const isSetupCompleted = useSelector(getIsSetupCompleted);
const dispatch = useDispatch();
const handleEngineChange = (engine?: string) => {
dispatch(updateDatabaseEngine(engine));
};
const handleDatabaseSubmit = async (database: DatabaseData) => {
try {
await dispatch(submitDatabase(database)).unwrap();
} catch (error) {
throw getSubmitError(error);
}
};
const handleCancel = useCallback(() => {
onStepCancel(engine);
}, [engine, onStepCancel]);
const handleInviteSubmit = (invite: InviteInfo) => {
dispatch(submitUserInvite(invite));
};
const handleStepSelect = () => {
dispatch(selectStep(DATABASE_STEP));
};
const handleStepCancel = () => {
dispatch(skipDatabase(engine));
};
if (!isStepActive) {
return (
......@@ -63,7 +75,7 @@ const DatabaseStep = ({
label={3}
isStepCompleted={isStepCompleted}
isSetupCompleted={isSetupCompleted}
onStepSelect={onStepSelect}
onStepSelect={handleStepSelect}
/>
);
}
......@@ -79,9 +91,9 @@ const DatabaseStep = ({
</StepDescription>
<DatabaseForm
initialValues={database}
onSubmit={handleSubmit}
onEngineChange={onEngineChange}
onCancel={handleCancel}
onSubmit={handleDatabaseSubmit}
onEngineChange={handleEngineChange}
onCancel={handleStepCancel}
/>
{isEmailConfigured && (
<SetupSection
......@@ -91,7 +103,7 @@ const DatabaseStep = ({
<InviteUserForm
user={user}
invite={invite}
onSubmit={onInviteSubmit}
onSubmit={handleInviteSubmit}
/>
</SetupSection>
)}
......@@ -120,6 +132,3 @@ const getSubmitError = (error: unknown): unknown => {
details: errors,
}));
};
// eslint-disable-next-line import/no-default-export -- deprecated usage
export default DatabaseStep;
import React from "react";
import { render, screen } from "@testing-library/react";
import { DatabaseData } from "metabase-types/api";
import { createMockDatabaseData } from "metabase-types/api/mocks";
import DatabaseStep, { DatabaseStepProps } from "./DatabaseStep";
import {
createMockSettingsState,
createMockSetupState,
createMockState,
} from "metabase-types/store/mocks";
import { renderWithProviders, screen } from "__support__/ui";
import { DATABASE_STEP, PREFERENCES_STEP } from "../../constants";
import { DatabaseStep } from "./DatabaseStep";
interface SetupOpts {
step?: number;
database?: DatabaseData;
isEmailConfigured?: boolean;
}
const setup = ({
step = DATABASE_STEP,
database,
isEmailConfigured = false,
}: SetupOpts = {}) => {
const state = createMockState({
setup: createMockSetupState({
step,
database,
}),
settings: createMockSettingsState({
"email-configured?": isEmailConfigured,
}),
});
const ComponentMock = () => <div />;
jest.mock("metabase/databases/containers/DatabaseForm", () => ComponentMock);
renderWithProviders(<DatabaseStep />, { storeInitialState: state });
};
describe("DatabaseStep", () => {
it("should render in active state", () => {
const props = getProps({
isStepActive: true,
isStepCompleted: false,
});
render(<DatabaseStep {...props} />);
setup();
expect(screen.getByText("Add your data")).toBeInTheDocument();
});
it("should render in completed state", () => {
const props = getProps({
setup({
step: PREFERENCES_STEP,
database: createMockDatabaseData({ name: "Test" }),
isStepActive: false,
isStepCompleted: true,
});
render(<DatabaseStep {...props} />);
expect(screen.getByText("Connecting to Test")).toBeInTheDocument();
});
it("should render a user invite form", () => {
const props = getProps({
isStepActive: true,
setup({
isEmailConfigured: true,
});
render(<DatabaseStep {...props} />);
expect(
screen.getByText("Need help connecting to your data?"),
).toBeInTheDocument();
});
});
const getProps = (opts?: Partial<DatabaseStepProps>): DatabaseStepProps => ({
isEmailConfigured: false,
isStepActive: false,
isStepCompleted: false,
isSetupCompleted: false,
onEngineChange: jest.fn(),
onStepSelect: jest.fn(),
onDatabaseSubmit: jest.fn(),
onInviteSubmit: jest.fn(),
onStepCancel: jest.fn(),
...opts,
});
// eslint-disable-next-line import/no-default-export -- deprecated usage
export { default } from "./DatabaseStep";
export * from "./DatabaseStep";
......@@ -7,7 +7,7 @@ import {
StepLabelText,
} from "./InactiveStep.styled";
export interface InactiveStepProps {
interface InactiveStepProps {
title: string;
label: number;
isStepCompleted: boolean;
......@@ -15,7 +15,7 @@ export interface InactiveStepProps {
onStepSelect: () => void;
}
const InactiveStep = ({
export const InactiveStep = ({
title,
label,
isStepCompleted,
......@@ -38,6 +38,3 @@ const InactiveStep = ({
</StepRoot>
);
};
// eslint-disable-next-line import/no-default-export -- deprecated usage
export default InactiveStep;
// eslint-disable-next-line import/no-default-export -- deprecated usage
export { default } from "./InactiveStep";
export * from "./InactiveStep";
......@@ -28,7 +28,7 @@ interface InviteUserFormProps {
onSubmit: (invite: InviteInfo) => void;
}
const InviteUserForm = ({
export const InviteUserForm = ({
user,
invite,
onSubmit,
......@@ -70,6 +70,3 @@ const InviteUserForm = ({
</FormProvider>
);
};
// eslint-disable-next-line import/no-default-export -- deprecated usage
export default InviteUserForm;
// eslint-disable-next-line import/no-default-export -- deprecated usage
export { default } from "./InviteUserForm";
export * from "./InviteUserForm";
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment