Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into thisagi
Browse files Browse the repository at this point in the history
  • Loading branch information
thisagi committed Oct 1, 2024
2 parents 0be1868 + 60759ed commit a3b3836
Show file tree
Hide file tree
Showing 18 changed files with 293 additions and 54 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
uses: actions/checkout@v4

- name: Setup | Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20
# node-version-file: .nvmrc
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Mac や WSL では [mise](https://mise.jdx.dev/getting-started.html) 等を経
```sh
npm install
```
`.env.example` ファイルをコピーして `.env` ファイルを作成し、環境変数を設定してください。(イコールの間に空白を入れないよう注意)

## 開発

Expand Down
7 changes: 7 additions & 0 deletions app/components/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { ReactNode } from "react";

export const Loading = (): ReactNode => (
<div className="flex h-dvh w-dvw items-center justify-center">
<div className="h-32 w-32 animate-spin rounded-full border-gray-900 border-t-2 border-b-2" />
</div>
);
41 changes: 41 additions & 0 deletions app/hooks/useSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Session } from "@supabase/supabase-js";
import useSWRImmutable from "swr/immutable";
import useSWRSubscription from "swr/subscription";
import type { SWRSubscriptionOptions } from "swr/subscription";
import { supabase } from "~/libs/supabase";

export function useSession(): Session | null {
const key = "session";
const { data: initialSession } = useSWRImmutable(
key,
async () => {
const {
data: { session },
} = await supabase.auth.getSession();

return session;
},
{
suspense: true,
},
);

const { data: session } = useSWRSubscription(
key,
(_, { next }: SWRSubscriptionOptions<Session | null, unknown>) => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
next(null, session);
});

return subscription.unsubscribe;
},
{
suspense: true,
fallbackData: initialSession,
},
);

return session ?? null;
}
11 changes: 11 additions & 0 deletions app/libs/getAccountCredentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { SignInWithPasswordCredentials } from "@supabase/supabase-js";

export function getAccountCredentials(
id: string,
): SignInWithPasswordCredentials {
return {
// supabase は userId のみでサインインできないため、強引にやる
email: `email-${id}@example.com`,
password: "password",
};
}
6 changes: 6 additions & 0 deletions app/libs/supabase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createClient } from "@supabase/supabase-js";

export const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
);
8 changes: 7 additions & 1 deletion app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
ScrollRestoration,
} from "@remix-run/react";
import "./tailwind.css";
import { Suspense } from "react";
import { Loading } from "./components/Loading";

export function Layout({ children }: { children: React.ReactNode }) {
return (
Expand All @@ -26,5 +28,9 @@ export function Layout({ children }: { children: React.ReactNode }) {
}

export default function App() {
return <Outlet />;
return (
<Suspense fallback={<Loading />}>
<Outlet />
</Suspense>
);
}
43 changes: 43 additions & 0 deletions app/routes/_auth._index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { MetaFunction } from "@remix-run/node";
import { useSession } from "~/hooks/useSession";

export const meta: MetaFunction = () => {
return [
{ title: "New Remix App" },
{ name: "description", content: "Welcome to Remix!" },
];
};

export default function Index() {
const session = useSession();

// 未サインインの場合
if (!session)
return (
<main className="grid h-dvh w-dvw grid-rows-2">
<h1 className="p-4 text-center text-4xl">Game</h1>
<div className="mx-auto flex max-w-96 flex-col gap-2 p-4">
<a
href="/signup"
type="button"
className="rounded-lg bg-pink-500 px-4 py-2 text-center font-bold text-white"
>
匿名で登録
</a>
</div>
</main>
);

// サインイン済みの場合
return (
<main className="grid h-dvh w-dvw grid-rows-2">
<h1 className="p-4 text-center text-4xl">Game</h1>
<div className="mx-auto flex max-w-96 flex-col gap-2 p-4">
Hello World
</div>
<div className="mx-auto flex max-w-96 flex-col gap-2 p-4">
ID: {session.user.email?.replace("@example.com", "")}
</div>
</main>
);
}
17 changes: 17 additions & 0 deletions app/routes/_auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Outlet, useNavigate } from "@remix-run/react";
import { type ReactNode, useLayoutEffect } from "react";
import { useSession } from "~/hooks/useSession";

// _auth.**.tsx のパスへのアクセスは必ずここで前処理される
export default function Layout(): ReactNode {
const navigate = useNavigate();
const session = useSession();

useLayoutEffect(() => {
if (!session) {
navigate("/signup");
}
}, [session, navigate]);

return <Outlet />;
}
48 changes: 0 additions & 48 deletions app/routes/_index.tsx

This file was deleted.

55 changes: 55 additions & 0 deletions app/routes/_noauth.signin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useNavigate } from "@remix-run/react";
import type { ReactNode } from "react";
import { useForm } from "react-hook-form";
import { getAccountCredentials } from "~/libs/getAccountCredentials";
import { supabase } from "~/libs/supabase";

type FormValues = {
id: string;
};

export default function Signin(): ReactNode {
// ID でサインイン、デバッグ用(そもそもログアウトは想定していない)
const navigate = useNavigate();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
mode: "onBlur",
});

const onSubmit = async ({ id }: FormValues) => {
const { error } = await supabase.auth.signInWithPassword(
getAccountCredentials(id),
);

if (error) {
console.error(error);
return;
}

navigate("/");
};

return (
<main>
<h1 className="p-4 text-center text-4xl">Game</h1>
<h2 className="p-4 text-center text-2xl">ID でサインイン</h2>
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="text"
className="rounded-lg bg-gray-100 px-4 py-2 text-center font-bold outline-pink-500"
placeholder="ID"
{...register("id")}
/>
</form>
<button
type="submit"
className="rounded-lg bg-pink-500 px-4 py-2 text-center font-bold text-white"
>
サインイン
</button>
</main>
);
}
34 changes: 34 additions & 0 deletions app/routes/_noauth.signup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useNavigate } from "@remix-run/react";
import { v4 } from "uuid";
import { getAccountCredentials } from "~/libs/getAccountCredentials";
import { supabase } from "~/libs/supabase";

export default function Signup() {
const navigate = useNavigate();

const signUp = async () => {
const id = v4();
const { error } = await supabase.auth.signUp(getAccountCredentials(id));

if (error) {
console.error("Error signing up:", error.message);
return;
}

navigate("/");
};
return (
<main className="grid h-dvh w-dvw grid-rows-2">
<h1 className="p-4 text-center text-4xl">Game</h1>
<div className="mx-auto flex max-w-96 flex-col gap-2 p-4">
<button
type="button"
className="rounded-lg bg-pink-500 px-4 py-2 text-center font-bold text-white"
onClick={signUp}
>
匿名で登録
</button>
</div>
</main>
);
}
19 changes: 19 additions & 0 deletions app/routes/_noauth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Outlet, useNavigate } from "@remix-run/react";
import type { ReactNode } from "react";
import { useLayoutEffect } from "react";

import { useSession } from "~/hooks/useSession";

// _noauth.**.tsx のパスへのアクセスは必ずここで前処理される
export default function Layout(): ReactNode {
const navigate = useNavigate();
const session = useSession();

useLayoutEffect(() => {
if (session) {
navigate("/");
}
}, [session, navigate]);

return <Outlet />;
}
5 changes: 4 additions & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"linter": {
"enabled": true,
"rules": {
"recommended": true
"recommended": true,
"nursery": {
"useSortedClasses": "warn"
}
}
},
"formatter": {
Expand Down
Loading

0 comments on commit a3b3836

Please sign in to comment.