Skip to content

Commit

Permalink
feat(wip): ユーザー登録部分を仮実装 (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
SnO2WMaN authored Oct 18, 2021
1 parent 9cfce14 commit 87e8bbc
Show file tree
Hide file tree
Showing 30 changed files with 672 additions and 16 deletions.
1 change: 1 addition & 0 deletions .env.development.local.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
NEXT_PUBLIC_MSW_ENABLED="true"
NEXT_PUBLIC_MSW_FIRST_NEED_REGISTER="true"
NEXT_PUBLIC_MOCK_AUTH0_ENABLED="true"

NEXT_PUBLIC_GRAPHQL_API_ENDPOINT="http://localhost:4000/graphql"
Expand Down
4 changes: 4 additions & 0 deletions .graphql/user.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ extend type User {
isFollowed(id: ID!): Boolean!
canPostHenken(id: ID!): Boolean!
}

extend type Query{
isAliasUnique(alias:String!):Boolean!
}
39 changes: 39 additions & 0 deletions codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,42 @@ generates:
- schema-ast
config:
sort: true

./src/auth/codegen.ts:
documents:
- "src/auth/**/*.{ts,tsx}"
- "!src/auth/**/*.codegen.ts"
plugins:
- typescript
- typescript-operations
- typescript-urql
config:
scalars:
DateTime: string

./src/components/codegen.ts:
documents:
- "src/components/**/*.{ts,tsx}"
- "!src/components/**/*.codegen.ts"
plugins:
- typescript
- typescript-operations
- typescript-urql
config:
scalars:
DateTime: string
nonOptionalTypename: true

./src/mocks/codegen.ts:
documents:
- "src/**/*.{ts,tsx}"
- "!src/**/codegen.ts"
- "!src/**/*.codegen.ts"
plugins:
- typescript
- typescript-operations
- typed-document-node
config:
nonOptionalTypename: true
scalars:
DateTime: string
3 changes: 2 additions & 1 deletion dprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"indentWidth": 2,
"quoteStyle": "alwaysDouble",
"operatorPosition": "sameLine",
"conditionalExpression.operatorPosition": "nextLine"
"conditionalExpression.operatorPosition": "nextLine",
"taggedTemplate.spaceBeforeLiteral": false
},
"json": {},
"markdown": {},
Expand Down
Binary file added public/.mock/auth0_picture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/.mock/viewer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ type Query {
findRecommendation(id: ID!): FindRecommendationPayload!
findUser(alias: String, id: ID): FindUserPayload!
henken(id: ID!): Henken!
isAliasUnique(alias: String!): Boolean!
manyAnswers(after: String, before: String, first: Int, last: Int, orderBy: AnswerOrder!): AnswerConnection!
manyAuthors(after: String, before: String, first: Int, last: Int, orderBy: AuthorOrder!): AuthorConnection!
manyBookSeries(after: String, before: String, first: Int, last: Int, orderBy: BookSeriesOrder!): BookSeriesConnection!
Expand Down
9 changes: 9 additions & 0 deletions src/auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Auth0Provider } from "@auth0/auth0-react";

import { MockedAuth0Provider } from "./mocks/MockedAuth0Provider";

export const AuthProvider =
// eslint-disable-next-line no-process-env
process.env.NEXT_PUBLIC_MOCK_AUTH0_ENABLED === "true"
? MockedAuth0Provider
: Auth0Provider;
37 changes: 37 additions & 0 deletions src/auth/FetchViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import gql from "graphql-tag";
import React, { useEffect } from "react";

import { useFetchViewerQuery } from "./codegen";
import { useSetViewer } from "./useViewer";

import { useAuth } from "~/auth/useAuth";

const FetchViewerQuery = gql`
query FetchViewer {
viewer {
id
alias
displayName
avatar
}
}
`;

export const FetchViewer: React.VFC = () => {
const { isAuthenticated } = useAuth();
const setter = useSetViewer();
const [result] = useFetchViewerQuery({ pause: !isAuthenticated });

const { data } = result;

useEffect(() => {
if (data?.viewer) {
const { __typename, ...viewer } = data.viewer;
setter(viewer);
} else if (data?.viewer === null) {
setter(null);
}
}, [data, setter]);

return <></>;
};
3 changes: 3 additions & 0 deletions src/auth/mocks/MockedAuth0Provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React from "react";

export const MockedAuth0Provider: React.FC = ({ children }) => <>{children}</>;
51 changes: 51 additions & 0 deletions src/auth/mocks/useMockedAuth0.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useAuth0 } from "@auth0/auth0-react";
import { atom, useRecoilValue, useSetRecoilState } from "recoil";

export const authenticatedState = atom<boolean>({
key: "mocked_authenticated",
default: false,
});

export const useMockedAuth0 = (): Pick<
ReturnType<typeof useAuth0>,
| "isAuthenticated"
| "user"
| "getAccessTokenSilently"
| "loginWithRedirect"
> => {
const isAuthenticated = useRecoilValue(authenticatedState);
const setAuthenticated = useSetRecoilState(authenticatedState);

if (isAuthenticated) {
return ({
isAuthenticated,

user: {
name: "TestViewer",
picture: "/.mock/auth0_picture.png",
},

async getAccessTokenSilently() {
return "access_token";
},

async loginWithRedirect() {
setAuthenticated(true);
},
});
} else {
return ({
isAuthenticated: false,

user: undefined,

async getAccessTokenSilently() {
return "access_token";
},

async loginWithRedirect() {
setAuthenticated(true);
},
});
}
};
9 changes: 9 additions & 0 deletions src/auth/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useAuth0 } from "@auth0/auth0-react";

import { useMockedAuth0 } from "./mocks/useMockedAuth0";

export const useAuth =
// eslint-disable-next-line no-process-env
process.env.NEXT_PUBLIC_MOCK_AUTH0_ENABLED === "true"
? useMockedAuth0
: useAuth0;
5 changes: 5 additions & 0 deletions src/auth/useViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export const useViewer = (): Viewer | null | undefined => {
return viewer;
};

export const useSetViewer = () => {
const setter = useSetRecoilState(viewerState);
return (viewer: Viewer | null) => setter(viewer);
};

export const useUpdateViewer = () => {
const setter = useSetRecoilState(viewerState);
return (viewer: Viewer) => setter(viewer);
Expand Down
25 changes: 25 additions & 0 deletions src/components/atoms/LoginButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import clsx from "clsx";
import React from "react";

import { useAuth } from "~/auth/useAuth";
import { useTranslation } from "~/i18n/useTranslation";

export const Component: React.VFC<{ className?: string; onClick(): void; }> = ({ className, onClick }) => {
const { LL } = useTranslation();
return (
<button
className={clsx(className)}
type="button"
onClick={() => onClick()}
onKeyPress={() => onClick()}
>
{LL.Login()}
</button>
);
};

export const LoginButton: React.VFC<{ className?: string; }> = ({ ...props }) => {
const { loginWithRedirect } = useAuth();

return <Component {...props} onClick={loginWithRedirect} />;
};
17 changes: 17 additions & 0 deletions src/components/atoms/Modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import clsx from "clsx";
import React from "react";
import ReactDOM from "react-dom";

export const Modal: React.FC<{ onClose(): void; }> = ({ children, onClose }) => {
return ReactDOM.createPortal(
<div className={clsx(["fixed", ["inset-0"], ["z-infinity"]], ["flex", ["items-center"], ["justify-center"]])}>
<div
className={clsx([["absolute"], ["inset-0"], ["z-minus"]], ["bg-black", ["bg-opacity-25"]])}
onClick={() => onClose()}
onKeyPress={() => onClose()}
/>
{children}
</div>,
document.body,
);
};
25 changes: 25 additions & 0 deletions src/components/atoms/RegisterButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import clsx from "clsx";
import React from "react";

import { useTranslation } from "~/i18n/useTranslation";
import { useOpenRegisterUserModal } from "~/modals/RegisterUser";

export const Component: React.VFC<{ className?: string; onClick(): void; }> = ({ className, onClick }) => {
const { LL } = useTranslation();
return (
<button
className={clsx(className)}
type="button"
onClick={() => onClick()}
onKeyPress={() => onClick()}
>
{LL.RegisterUser()}
</button>
);
};

export const RegisterButton: React.VFC<{ className?: string; }> = ({ ...props }) => {
const opener = useOpenRegisterUserModal();

return <Component {...props} onClick={opener} />;
};
54 changes: 54 additions & 0 deletions src/components/organisms/RegisterUserForm/Alias.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import clsx from "clsx";
import { gql } from "graphql-request";
import React from "react";
import { useFormContext } from "react-hook-form";
import { useClient } from "urql";

import { FormValue } from "./FormValue";

import {
RegisterUserIsAliasUniqueDocument,
RegisterUserIsAliasUniqueQuery,
RegisterUserIsAliasUniqueQueryVariables,
} from "~/components/codegen";
import { useTranslation } from "~/i18n/useTranslation";

const _RegisterUserIsAliasUniqueQuery = gql`
query RegisterUserIsAliasUnique($alias:String!){
isAliasUnique(alias:$alias)
}
`;

export const Alias: React.VFC<{ className?: string; }> = ({ className }) => {
const { LL } = useTranslation();
const { register } = useFormContext<FormValue>();
const client = useClient();

return (
<label
htmlFor="alias"
className={clsx(className, ["inline-flex", ["flex-col"]])}
>
<span className={clsx(["text-sm"])}>
{LL.RegisterUserForm.Alias.Label()}
</span>
<input
{...register("alias", {
minLength: 1,
maxLength: 15,
validate: {
unique: (alias) =>
client.query<RegisterUserIsAliasUniqueQuery, RegisterUserIsAliasUniqueQueryVariables>(
RegisterUserIsAliasUniqueDocument,
{ alias },
).toPromise().then(({ data }) => data?.isAliasUnique || false),
},
})}
name="alias"
type="text"
autoComplete="off"
className={clsx(["mt-2"])}
/>
</label>
);
};
32 changes: 32 additions & 0 deletions src/components/organisms/RegisterUserForm/DisplayName.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import clsx from "clsx";
import React from "react";
import { useFormContext } from "react-hook-form";
import { useClient } from "urql";

import { FormValue } from "./FormValue";

import { useTranslation } from "~/i18n/useTranslation";

export const DisplayName: React.VFC<{ className?: string; }> = ({ className }) => {
const { LL } = useTranslation();
const { register } = useFormContext<FormValue>();
const client = useClient();

return (
<label
htmlFor="alias"
className={clsx(className, ["inline-flex", ["flex-col"]])}
>
<span className={clsx(["text-sm"])}>
{LL.RegisterUserForm.DisplayName.Label()}
</span>
<input
{...register("displayName")}
name="alias"
type="text"
autoComplete="off"
className={clsx(["mt-2"])}
/>
</label>
);
};
5 changes: 5 additions & 0 deletions src/components/organisms/RegisterUserForm/FormValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type FormValue = {
alias: string;
displayName: string;
avatar: string;
};
27 changes: 27 additions & 0 deletions src/components/organisms/RegisterUserForm/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { action } from "@storybook/addon-actions";
import { Meta, Story } from "@storybook/react";
import React, { ComponentProps } from "react";
import { FormProvider, useForm } from "react-hook-form";

import { FormValue } from "./FormValue";

import { Component } from ".";

export default {
title: "RegisterUserForm",
component: Component,
argTypes: {},
} as Meta;

export const Primary: Story<ComponentProps<typeof Component>> = (args) => {
const methods = useForm<FormValue>();
return (
<FormProvider {...methods}>
<Component {...args} />
</FormProvider>
);
};
Primary.storyName = "通常";
Primary.args = {
onSubmit: action("submit"),
};
Loading

0 comments on commit 87e8bbc

Please sign in to comment.