Allow username and email to be changed

This commit is contained in:
Zoe Roux 2024-01-10 19:31:14 +01:00
parent 08d9b9d950
commit 65a254d808
10 changed files with 247 additions and 72 deletions

View File

@ -14,6 +14,7 @@
"format:fix": "prettier --write --ignore-path .gitignore '!src/utils/jotai-utils.tsx' ."
},
"dependencies": {
"@gorhom/portal": "^1.0.14",
"@kyoo/models": "workspace:^",
"@kyoo/primitives": "workspace:^",
"@kyoo/ui": "workspace:^",

View File

@ -44,6 +44,7 @@ import { withTranslations } from "../i18n";
import arrayShuffle from "array-shuffle";
import { Tooltip } from "react-tooltip";
import { getCurrentAccount, readCookie, updateAccount } from "@kyoo/models/src/account-internal";
import { PortalProvider } from "@gorhom/portal";
const font = Poppins({ weight: ["300", "400", "900"], subsets: ["latin"], display: "swap" });
@ -136,21 +137,23 @@ const App = ({ Component, pageProps }: AppProps) => {
<AccountProvider ssrAccount={account}>
<HydrationBoundary state={queryState}>
<ThemeSelector theme={userTheme} font={{ normal: "inherit" }}>
<GlobalCssTheme />
<Layout
page={
<Component
randomItems={
randomItems[Component.displayName!] ??
arrayShuffle((Component as QueryPage).randomItems ?? [])
}
{...props}
/>
}
randomItems={[]}
{...layoutProps}
/>
<Tooltip id="tooltip" positionStrategy={"fixed"} />
<PortalProvider>
<GlobalCssTheme />
<Layout
page={
<Component
randomItems={
randomItems[Component.displayName!] ??
arrayShuffle((Component as QueryPage).randomItems ?? [])
}
{...props}
/>
}
randomItems={[]}
{...layoutProps}
/>
<Tooltip id="tooltip" positionStrategy={"fixed"} />
</PortalProvider>
</ThemeSelector>
</HydrationBoundary>
</AccountProvider>

View File

@ -45,7 +45,7 @@ export const queryFn = async <Data,>(
| {
path: (string | false | undefined | null)[];
body?: object;
method: "GET" | "POST" | "DELETE";
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
authenticated?: boolean;
apiUrl?: string;
timeout?: number;

View File

@ -53,8 +53,16 @@ export const Button = forwardRef<
props as any,
)}
>
<P {...css({ textAlign: "center" }, "text")}>{text}</P>
{icon}
<View
{...css({
paddingX: ts(3),
flexDirection: "row",
alignItems: "center",
})}
>
<P {...css({ textAlign: "center" }, "text")}>{text}</P>
{icon}
</View>
</PressableFeedback>
);
});

View File

@ -31,6 +31,7 @@ import { useYoshiki } from "yoshiki";
import { PressableFeedback } from "./links";
import { P } from "./text";
import { focusReset, ts } from "./utils";
import { View } from "react-native";
export const Select = ({
label,
@ -68,10 +69,18 @@ export const Select = ({
},
})}
>
<P {...css({ textAlign: "center" }, "text")}>{<RSelect.Value />}</P>
<RSelect.Icon asChild>
<Icon icon={ExpandMore} />
</RSelect.Icon>
<View
{...css({
paddingX: ts(3),
flexDirection: "row",
alignItems: "center",
})}
>
<P {...css({ textAlign: "center" }, "text")}>{<RSelect.Value />}</P>
<RSelect.Icon asChild>
<Icon icon={ExpandMore} />
</RSelect.Icon>
</View>
</InternalTriger>
</RSelect.Trigger>
<ContrastArea mode="user">

View File

@ -8,12 +8,14 @@
"@kyoo/primitives": "workspace:^"
},
"devDependencies": {
"@gorhom/portal": "^1.0.14",
"@shopify/flash-list": "^1.6.3",
"@types/react": "18.2.39",
"react-native-uuid": "^2.0.1",
"typescript": "^5.3.2"
},
"peerDependencies": {
"@gorhom/portal": "*",
"@kesha-antonov/react-native-background-downloader": "*",
"@material-symbols/svg-400": "*",
"@shopify/flash-list": "^1.3.1",

View File

@ -19,25 +19,43 @@
*/
import {
Account,
MutationParam,
QueryIdentifier,
QueryPage,
User,
UserP,
queryFn,
setUserTheme,
useAccount,
useUserTheme,
} from "@kyoo/models";
import { Container, H1, HR, Icon, P, Select, SubP, imageBorderRadius, ts } from "@kyoo/primitives";
import {
Button,
Container,
H1,
HR,
Icon,
Input,
P,
Select,
SubP,
SwitchVariant,
imageBorderRadius,
ts,
} from "@kyoo/primitives";
import { DefaultLayout } from "../layout";
import { Children, ReactElement, ReactNode } from "react";
import { Children, ReactElement, ReactNode, useState, useTransition } from "react";
import { useTranslation } from "react-i18next";
import { ScrollView, View } from "react-native";
import { px, rem, useYoshiki } from "yoshiki/native";
import { Portal } from "@gorhom/portal";
import { percent, px, rem, useYoshiki } from "yoshiki/native";
import Theme from "@material-symbols/svg-400/outlined/dark_mode.svg";
import Username from "@material-symbols/svg-400/outlined/badge.svg";
import Mail from "@material-symbols/svg-400/outlined/mail.svg";
import Password from "@material-symbols/svg-400/outlined/password.svg";
import { useMutation, useQueryClient } from "@tanstack/react-query";
const Preference = ({
icon,
@ -89,20 +107,164 @@ const SettingsContainer = ({
return (
<Container>
<H1 {...css({ fontSize: rem(2) })}>{title}</H1>
<View
{...css({ bg: (theme) => theme.variant.background, borderRadius: px(imageBorderRadius) })}
>
{Children.map(children, (x, i) => (
<>
{i !== 0 && <HR {...css({ marginY: ts(1) })} />}
{x}
</>
))}
</View>
<SwitchVariant>
{({ css }) => (
<View
{...css({
bg: (theme) => theme.background,
borderRadius: px(imageBorderRadius),
})}
>
{Children.map(children, (x, i) => (
<>
{i !== 0 && <HR {...css({ marginY: ts(1) })} />}
{x}
</>
))}
</View>
)}
</SwitchVariant>
</Container>
);
};
const ChangePopup = ({
label,
icon,
inital,
apply,
close,
}: {
label: string;
icon: Icon;
inital: string;
apply: (v: string) => Promise<unknown>;
close: () => void;
}) => {
const { t } = useTranslation();
const [value, setValue] = useState(inital);
return (
<Portal>
<SwitchVariant>
{({ css }) => (
<View
{...css({
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
bg: (theme) => theme.themeOverlay,
})}
>
<Container
{...css({
borderRadius: px(imageBorderRadius),
position: "absolute",
top: percent(35),
padding: ts(4),
gap: ts(2),
bg: (theme) => theme.background,
})}
>
<View {...css({ flexDirection: "row", alignItems: "center", gap: ts(2) })}>
<Icon icon={icon} />
<H1 {...css({ fontSize: rem(2) })}>{label}</H1>
</View>
<Input variant="big" value={value} onChangeText={(v) => setValue(v)} />
<View {...css({ flexDirection: "row", alignSelf: "flex-end", gap: ts(1) })}>
<Button
text={t("misc.cancel")}
onPress={() => close()}
{...css({ minWidth: rem(6) })}
/>
<Button
text={t("misc.edit")}
onPress={async () => {
await apply(value);
close();
}}
{...css({ minWidth: rem(6) })}
/>
</View>
</Container>
</View>
)}
</SwitchVariant>
</Portal>
);
};
const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void }) => {
const account = useAccount();
const { css } = useYoshiki();
const { t } = useTranslation();
const queryClient = useQueryClient();
const { mutateAsync } = useMutation({
mutationFn: async (update: Partial<Account>) =>
await queryFn({
path: ["auth", "me"],
method: "PATCH",
body: update,
}),
onSettled: async () => await queryClient.invalidateQueries({ queryKey: ["auth", "me"] }),
});
return (
account && (
<SettingsContainer title={t("settings.account.label")}>
<Preference
icon={Username}
label={t("settings.account.username.label")}
description={account.username}
>
<Button
text={t("misc.edit")}
onPress={() =>
setPopup(
<ChangePopup
icon={Username}
label={t("settings.account.username.label")}
inital={account.username}
apply={async (v) => await mutateAsync({ username: v })}
close={() => setPopup(undefined)}
/>,
)
}
/>
</Preference>
<Preference
icon={Mail}
label={t("settings.account.email.label")}
description={account.email}
>
<Button
text={t("misc.edit")}
onPress={() =>
setPopup(
<ChangePopup
icon={Mail}
label={t("settings.account.email.label")}
inital={account.email}
apply={async (v) => await mutateAsync({ email: v })}
close={() => setPopup(undefined)}
/>,
)
}
/>
</Preference>
<Preference
icon={Password}
label={t("settings.account.password.label")}
description={t("settings.account.password.description")}
></Preference>
</SettingsContainer>
)
);
};
const query: QueryIdentifier<User> = {
parser: UserP,
path: ["auth", "me"],
@ -110,46 +272,31 @@ const query: QueryIdentifier<User> = {
export const SettingsPage: QueryPage = () => {
const { t } = useTranslation();
const [popup, setPopup] = useState<ReactElement | undefined>(undefined);
const theme = useUserTheme("auto");
const account = useAccount();
return (
<ScrollView contentContainerStyle={{ gap: ts(4) }}>
<SettingsContainer title={t("settings.general.label")}>
<Preference
icon={Theme}
label={t("settings.general.theme.label")}
description={t("settings.general.theme.description")}
>
<Select
<>
<ScrollView contentContainerStyle={{ gap: ts(4) }}>
<SettingsContainer title={t("settings.general.label")}>
<Preference
icon={Theme}
label={t("settings.general.theme.label")}
value={theme}
onValueChange={(value) => setUserTheme(value)}
values={["auto", "light", "dark"]}
getLabel={(key) => t(`settings.general.theme.${key}`)}
/>
</Preference>
</SettingsContainer>
{account && (
<SettingsContainer title={t("settings.account.label")}>
<Preference
icon={Username}
label={t("settings.account.username.label")}
description={account.username}
></Preference>
<Preference
icon={Mail}
label={t("settings.account.email.label")}
description={account.email}
></Preference>
<Preference
icon={Password}
label={t("settings.account.password.label")}
description={t("settings.account.password.description")}
></Preference>
description={t("settings.general.theme.description")}
>
<Select
label={t("settings.general.theme.label")}
value={theme}
onValueChange={(value) => setUserTheme(value)}
values={["auto", "light", "dark"]}
getLabel={(key) => t(`settings.general.theme.${key}`)}
/>
</Preference>
</SettingsContainer>
)}
</ScrollView>
<AccountSettings setPopup={setPopup} />
</ScrollView>
{popup}
</>
);
};

View File

@ -66,7 +66,8 @@
"cancel": "Cancel",
"more": "More",
"expand": "Expand",
"collapse": "Collapse"
"collapse": "Collapse",
"edit": "Edit"
},
"navbar": {
"home": "Home",

View File

@ -66,7 +66,8 @@
"cancel": "Annuler",
"more": "Plus",
"expand": "Développer",
"collapse": "Replier"
"collapse": "Replier",
"edit": "Changer"
},
"navbar": {
"home": "Accueil",

View File

@ -2950,6 +2950,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@kyoo/ui@workspace:packages/ui"
dependencies:
"@gorhom/portal": ^1.0.14
"@kesha-antonov/react-native-background-downloader": ^2.10.0
"@kyoo/models": "workspace:^"
"@kyoo/primitives": "workspace:^"
@ -2960,6 +2961,7 @@ __metadata:
react-native-uuid: ^2.0.1
typescript: ^5.3.2
peerDependencies:
"@gorhom/portal": "*"
"@kesha-antonov/react-native-background-downloader": "*"
"@material-symbols/svg-400": "*"
"@shopify/flash-list": ^1.3.1
@ -15330,6 +15332,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "web@workspace:apps/web"
dependencies:
"@gorhom/portal": ^1.0.14
"@kyoo/models": "workspace:^"
"@kyoo/primitives": "workspace:^"
"@kyoo/ui": "workspace:^"