Skip to content

Commit

Permalink
Merge branch 'issues/124'
Browse files Browse the repository at this point in the history
closes: #124
  • Loading branch information
wappon28dev committed Sep 4, 2024
2 parents 97a9925 + cbe7296 commit b7c520b
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 112 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@nanostores/react": "^0.7.2",
"@radix-ui/themes": "^3.0.5",
"chart.js": "^4.4.3",
"emoji-picker-react": "^4.12.0",
"nanostores": "^0.10.3",
"openapi-fetch": "^0.9.7",
"react": "^18.2.0",
Expand Down
119 changes: 53 additions & 66 deletions src/hooks/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,92 +34,76 @@ export function useTeam() {

const fetchAbout = async (): Promise<
InferResponseType<"/teams/{team_name}", "get">
> => {
const result = await esaClient.GET(
"/teams/{team_name}",
paramsWithTeamName,
);

return await match(result)
> =>
await match(await esaClient.GET("/teams/{team_name}", paramsWithTeamName))
.with(A.Success, ({ data }) => data)
.otherwise(handleError);
};

const fetchStats = async (): Promise<
InferResponseType<"/teams/{team_name}/stats", "get">
> => {
const result = await esaClient.GET(
"/teams/{team_name}/stats",
paramsWithTeamName,
);
return await match(result)
> =>
await match(
await esaClient.GET("/teams/{team_name}/stats", paramsWithTeamName),
)
.with(A.Success, ({ data }) => data)
.otherwise(handleError);
};

const fetchMembers = async (): Promise<
InferResponseType<"/teams/{team_name}/members", "get">["members"]
> => {
const result = await esaClient.GET(
"/teams/{team_name}/members",
paramsWithTeamName,
);
return await match(result)
> =>
await match(
await esaClient.GET("/teams/{team_name}/members", paramsWithTeamName),
)
.with(A.Success, ({ data }) => data.members)
.otherwise(handleError);
};

const createNewPost = async (
postBody: InferRequestBodyType<"/teams/{team_name}/posts", "post">["post"],
): Promise<InferResponseType<"/teams/{team_name}/posts", "post", 201>> => {
const result = await esaClient.POST("/teams/{team_name}/posts", {
...paramsWithTeamName,
body: {
post: postBody,
},
});
return await match(result)
): Promise<InferResponseType<"/teams/{team_name}/posts", "post", 201>> =>
await match(
await esaClient.POST("/teams/{team_name}/posts", {
...paramsWithTeamName,
body: {
post: postBody,
},
}),
)
.with(A.Success, ({ data }) => data)
.otherwise(handleError);
};

const fetchPostsByCategory = async (
category: string,
): Promise<InferResponseType<"/teams/{team_name}/posts", "get">["posts"]> => {
const result = await esaClient.GET("/teams/{team_name}/posts", {
params: {
...paramsWithTeamName.params,
query: {
q: `on:${category}`,
): Promise<InferResponseType<"/teams/{team_name}/posts", "get">["posts"]> =>
await match(
await esaClient.GET("/teams/{team_name}/posts", {
params: {
...paramsWithTeamName.params,
query: {
q: `on:${category}`,
},
},
},
});

return await match(result)
}),
)
.with(A.Success, ({ data }) => data.posts)
.otherwise(handleError);
};

const fetchPostByPostId = async (
postNumber: number,
): Promise<
InferResponseType<"/teams/{team_name}/posts/{post_number}", "get">
> => {
const result = await esaClient.GET(
"/teams/{team_name}/posts/{post_number}",
{
> =>
await match(
await esaClient.GET("/teams/{team_name}/posts/{post_number}", {
params: {
path: {
...paramsWithTeamName.params.path,
post_number: postNumber,
},
},
},
);
return await match(result)
}),
)
.with(A.Success, ({ data }) => data)
.otherwise(handleError);
};

const updatePost = async (
postNumber: number,
Expand All @@ -129,50 +113,53 @@ export function useTeam() {
>,
): Promise<
InferResponseType<"/teams/{team_name}/posts/{post_number}", "patch">
> => {
const result = await esaClient.PATCH(
"/teams/{team_name}/posts/{post_number}",
{
> =>
await match(
await esaClient.PATCH("/teams/{team_name}/posts/{post_number}", {
params: {
path: {
...paramsWithTeamName.params.path,
post_number: postNumber,
},
},
body,
},
);

return await match(result)
}),
)
.with(A.Success, ({ data }) => data)
.otherwise(handleError);
};

const deletePost = async (postNumber: number): Promise<void> => {
const result = await esaClient.DELETE(
"/teams/{team_name}/posts/{post_number}",
{
await match(
await esaClient.DELETE("/teams/{team_name}/posts/{post_number}", {
params: {
path: {
...paramsWithTeamName.params.path,
post_number: postNumber,
},
},
},
);

await match(result)
}),
)
.with(A.Success, () => undefined)
.otherwise(handleError);
};

const fetchEmojis = async (): Promise<
InferResponseType<"/teams/{team_name}/emojis", "get">
> =>
await match(
await esaClient.GET("/teams/{team_name}/emojis", paramsWithTeamName),
)
.with(A.Success, ({ data }) => data)
.otherwise(handleError);

return {
selectedTeamName,
fetchStats,
fetchAbout,
fetchMembers,
fetchPostByPostId,
fetchPostsByCategory,
fetchEmojis,

__createNewPost: createNewPost,
__updatePost: updatePost,
Expand Down
34 changes: 34 additions & 0 deletions src/lib/utils/emoji.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { type CustomEmoji } from "emoji-picker-react/dist/config/customEmojiConfig";

export function emojiUnified2url(emojiUnified: string): string {
return `https://cdn.jsdelivr.net/gh/twitter/[email protected]/assets/svg/${emojiUnified}.svg`;
}

export const SPECIAL_EMOJIS = [
{
id: "fail-a-class",
names: ["落単"],
imgUrl:
"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUqLcDsoa_vMqHK_IPFR4GoMT9RYnH6gtzw9nqHl2AfJeQI7Bm6vd2LphkvWznofSU0yXGcFCWEmO1owcCDJKqaijH4sDyK6r7gwjHUoqD-lVYxHPO9m6khg559gSY2FVv9qia_dHQPxbQ/s800/school_tani_otosu_boy.png",
},
{
id: "capybara-dancing",
names: ["ダンスカピバラ"],
imgUrl: "https://qr.paps.jp/8o3Og",
},
{
id: "capybara-bath",
names: ["お風呂カピバラ"],
imgUrl: "https://i.imgur.com/5TaVIlf.gif",
},
{
id: "capybara-bath-2",
names: ["お風呂カピバラ2"],
imgUrl: "https://qr.paps.jp/fblo0",
},
{
id: "capybara-munching",
names: ["むしゃむしゃカピバラ"],
imgUrl: "https://i.gifer.com/9ZNS.gif",
},
] as const satisfies CustomEmoji[];
107 changes: 61 additions & 46 deletions src/pages/create/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,19 @@ import {
IconButton,
Popover,
} from "@radix-ui/themes";
import { useState, type ReactElement } from "react";
import EmojiPicker, { EmojiStyle } from "emoji-picker-react";
import { type CustomEmoji } from "emoji-picker-react/dist/config/customEmojiConfig";
import { useMemo, useState, type ReactElement } from "react";
import { type SubmitHandler, useForm } from "react-hook-form";
import styled from "styled-components";
import useSWRImmutable from "swr/immutable";
import { match } from "ts-pattern";
import { ErrorScreen } from "@/components/ErrorScreen";
import { useAchievements } from "@/hooks/db/achievements";
import { useTeam } from "@/hooks/teams";
import { S } from "@/lib/consts";
import { emojiUnified2url, SPECIAL_EMOJIS } from "@/lib/utils/emoji";
import { handleSWRError } from "@/lib/utils/swr";
import yup from "@/lib/yup-locate";
import { type Achievement, yAchievement } from "@/types/post-data/achievements";

Expand Down Expand Up @@ -110,6 +117,7 @@ const SubmitButton = styled.input`
`;

const PlusButton = styled(IconButton)`
cursor: pointer;
position: absolute;
top: 164px;
left: 132px;
Expand Down Expand Up @@ -169,30 +177,13 @@ const yAchievementForm = yAchievement.concat(
}),
);

const ICON_URLS = [
"https://api.iconify.design/twemoji:trophy.svg",
"https://api.iconify.design/twemoji:meat-on-bone.svg",
"https://api.iconify.design/twemoji:horse-racing-medium-skin-tone.svg",
"https://api.iconify.design/twemoji:steaming-bowl.svg",
"https://api.iconify.design/twemoji:shopping-cart.svg",
"https://api.iconify.design/twemoji:page-facing-up.svg",
"https://api.iconify.design/twemoji:laptop.svg",
"https://api.iconify.design/twemoji:zombie.svg",
"https://api.iconify.design/twemoji:face-with-symbols-on-mouth.svg",
"https://api.iconify.design/twemoji:broken-heart.svg",
"https://api.iconify.design/twemoji:fire.svg",
"https://api.iconify.design/twemoji:beer-mug.svg",
"https://api.iconify.design/twemoji:beetle.svg",
"https://api.iconify.design/twemoji:bathtub.svg",
"https://api.iconify.design/twemoji:broccoli.svg",
"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUqLcDsoa_vMqHK_IPFR4GoMT9RYnH6gtzw9nqHl2AfJeQI7Bm6vd2LphkvWznofSU0yXGcFCWEmO1owcCDJKqaijH4sDyK6r7gwjHUoqD-lVYxHPO9m6khg559gSY2FVv9qia_dHQPxbQ/s800/school_tani_otosu_boy.png",
"https://qr.paps.jp/8o3Og",
"https://i.imgur.com/5TaVIlf.gif",
"https://qr.paps.jp/fblo0",
"https://i.gifer.com/9ZNS.gif",
] as const;

export default function Page(): ReactElement {
function Loaded({
emojis,
}: {
emojis: Awaited<
ReturnType<ReturnType<typeof useTeam>["fetchEmojis"]>
>["emojis"];
}): ReactElement {
const {
register,
handleSubmit,
Expand All @@ -206,13 +197,26 @@ export default function Page(): ReactElement {
reValidateMode: "onChange",
resolver: yupResolver(yAchievementForm),
defaultValues: {
icon: ICON_URLS[0],
icon: emojiUnified2url("1f3c6"),
},
});
const { errors, isSubmitting } = formState;
const { fetch, update } = useAchievements(useTeam);

const [isPopoverOpened, setPopoverOpened] = useState(false);
const additionalEmojis = useMemo<CustomEmoji[]>(
() =>
emojis
// メンバー絵文字を除外
// ref: https://docs.esa.io/posts/230#%E3%83%A1%E3%83%B3%E3%83%90%E3%83%BC%E7%B5%B5%E6%96%87%E5%AD%97
.filter(({ code }) => !code.startsWith("@"))
.map(({ code, aliases, url }) => ({
id: code,
names: aliases,
imgUrl: url,
})),
[emojis],
);

const onSubmit: SubmitHandler<
yup.InferType<typeof yAchievementForm>
Expand Down Expand Up @@ -257,26 +261,20 @@ export default function Page(): ReactElement {
<Icon icon="ion:add" width="30px" />
</PlusButton>
</Popover.Trigger>
<Popover.Content maxWidth="300px" size="1">
<Text as="p" size="1" trim="both">
{ICON_URLS.map((url) => (
<IconButton
key={url}
disabled={isSubmitting}
m="2"
onClick={() => {
setValue("icon", url);
setPopoverOpened(false);
}}
size="4"
value={getValues("icon")}
variant="ghost"
{...register("icon")}
>
<Avatar fallback="A" size="4" src={url} />
</IconButton>
))}
</Text>
<Popover.Content>
<EmojiPicker
customEmojis={[...additionalEmojis, ...SPECIAL_EMOJIS]}
emojiStyle={EmojiStyle.TWITTER}
onEmojiClick={(emoji) => {
setValue(
"icon",
emoji.isCustom
? emoji.imageUrl
: emojiUnified2url(emoji.unified),
);
setPopoverOpened(false);
}}
/>
</Popover.Content>
</Popover.Root>
<AvatarStyle
Expand Down Expand Up @@ -385,3 +383,20 @@ export default function Page(): ReactElement {
</form>
);
}

export default function Page(): ReactElement {
const { fetchEmojis } = useTeam();
const swrEmojis = useSWRImmutable("emojis", fetchEmojis);

return match(swrEmojis)
.with(S.Loading, () => (
<Flex gap="10px">
<Icon height="1em" icon="svg-spinners:ring-resize" />
<p>カスタム絵文字を取得中...</p>
</Flex>
))
.with(S.Success, ({ data: { emojis } }) => <Loaded emojis={emojis} />)
.otherwise(({ data, error }) => (
<ErrorScreen error={handleSWRError(data, error)} />
));
}

0 comments on commit b7c520b

Please sign in to comment.