Skip to content

Commit

Permalink
Merge pull request #155 from h8570rg/develop
Browse files Browse the repository at this point in the history
Prd
  • Loading branch information
h8570rg authored May 19, 2024
2 parents 15f6e65 + 6921e0d commit b32a047
Show file tree
Hide file tree
Showing 171 changed files with 11,209 additions and 9,644 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
NEXT_PUBLIC_SERVICE_NAME=janreco
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
GOOGLE_CLIENT_ID=
GOOGLE_SECRET=
OPENAI_API_KEY=
13 changes: 12 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
],
"pathGroups": [
{
"pattern": "~/**",
"pattern": "@/**",
"group": "internal"
},
{
Expand All @@ -42,6 +42,17 @@
{
"namedComponents": "function-declaration"
}
],
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "dayjs",
"message": "Please import from '@/lib/utils/date' instead."
}
]
}
]
}
}
4 changes: 4 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ on:
jobs:
test:
runs-on: ubuntu-22.04
env:
GOOGLE_CLIENT_ID: "dummy"
GOOGLE_SECRET: "dummy"
OPENAI_API_KEY: "dummy"
steps:
- uses: actions/checkout@v3

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ jobs:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_DB_PASSWORD: ${{ secrets.PRODUCTION_DB_PASSWORD }}
PRODUCTION_PROJECT_ID: owagpoywxhbsckytdtoj
GOOGLE_CLIENT_ID: "dummy"
GOOGLE_SECRET: "dummy"
OPENAI_API_KEY: "dummy"

steps:
- uses: actions/checkout@v3
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ jobs:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_DB_PASSWORD: ${{ secrets.STAGING_DB_PASSWORD }}
STAGING_PROJECT_ID: ggkmppnjhrwzdsamzqbp
GOOGLE_CLIENT_ID: "dummy"
GOOGLE_SECRET: "dummy"
OPENAI_API_KEY: "dummy"

steps:
- uses: actions/checkout@v3
Expand Down
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
node_modules
.next
.npmrc
.env.local
.vscode
.env*
tsconfig.tsbuildinfo
13 changes: 13 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"cSpell.words": [
"fkey",
"janreco",
"nextjs",
"nextui",
"Noto",
"OPENAI",
"Postgrest",
"svgr",
"toastify"
]
}
75 changes: 11 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,68 +70,15 @@ supabase studio url: http://localhost:54323

- [supabase](https://supabase.com/docs)

# Sequence

## Client Component

```mermaid
sequenceDiagram
box Client
participant CC as Client Component
participant H as Hooks
end
box Next Server
participant AR as Api Routes
participant SC as Server Component
participant SV as Service
participant MD as Model
participant RP as Repository
end
box Supabase
participant SB as Supabase
end
%% server component
SC->>SV:
SV->>RP:
RP->>SB:
SB->>RP:
RP->>SV:
SV->>MD:
MD->>SV:
SV->>SC:
```

## Server Component

```mermaid
sequenceDiagram
box Client
participant CC as Client Component
participant H as Hooks
end
box Next Server
participant AR as Api Routes
participant SC as Server Component
participant SV as Service
participant MD as Model
participant RP as Repository
end
box Supabase
participant SB as Supabase
end
%% server component
CC->>H:
H->>AR:
AR->>SV:
SV->>RP:
RP->>SB:
SB->>RP:
RP->>SV:
SV->>MD:
MD->>SV:
SV->>AR:
AR->>H:
H->>CC:
```
# Rules

## Words

| code | 意味 |
| ---- | ---- |
| user | ログインしているユーザー |
| match | 成績表 |
| game | 半荘 |
| score | ポイント |
| points | 点数|
16 changes: 16 additions & 0 deletions app/(auth)/(routes)/auth-code-error/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Link from "next/link";
import { Button } from "@/components/Button";

/**
* @see https://supabase.com/docs/guides/auth/server-side/oauth-with-pkce-flow-for-ssr
*/
export default function AuthCodeErrorPage() {
return (
<div className="flex flex-col items-center">
<h1>ログインに失敗しました</h1>
<Button as={Link} href="/login" className="mt-4" color="primary">
ログイン画面へ戻る
</Button>
</div>
);
}
84 changes: 84 additions & 0 deletions app/(auth)/(routes)/login/(components)/LoginForm/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use server";

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { z } from "zod";
import { schema } from "@/lib/utils/schema";
import { createClient } from "@/lib/utils/supabase/server";
import { getURL } from "@/lib/utils/url";

type State = {
errors?: {
base?: string[];
email?: string[];
password?: string[];
};
};

const signInEmailSchema = z.object({
email: schema.email,
password: schema.password,
});

/**
* @see https://supabase.com/docs/guides/auth/server-side/nextjs
*/
export async function signInEmail(
prevState: State,
formData: FormData,
): Promise<State> {
const validatedFields = signInEmailSchema.safeParse({
email: formData.get("email"),
password: formData.get("password"),
});

if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
};
}

const { email, password } = validatedFields.data;

const supabase = createClient();

const { error } = await supabase.auth.signInWithPassword({
email,
password,
});

if (error) {
return {
errors: {
base: ["メールアドレスまたはパスワードが間違っています。"],
},
};
}

revalidatePath("/", "layout");
redirect("/");
}

/**
* @see https://supabase.com/docs/guides/auth/server-side/oauth-with-pkce-flow-for-ssr?queryGroups=environment&environment=server
*/
export async function signInWithGoogle() {
const supabase = createClient();
const { data } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${getURL()}api/auth/callback`,
},
});

if (data.url) {
redirect(data.url); // use the redirect API for your server framework
}
}

export async function signInAnonymously() {
const supabase = createClient();
await supabase.auth.signInAnonymously();

redirect("/");
}
44 changes: 44 additions & 0 deletions app/(auth)/(routes)/login/(components)/LoginForm/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import { useFormState } from "react-dom";
import { Button } from "@/components/Button";
import { Input } from "@/components/Input";
import { signInEmail } from "./actions";

/**
* @see https://supabase.com/docs/guides/auth/server-side/nextjs
*/
export function LoginForm({ className }: { className?: string }) {
const [state, formAction] = useFormState(signInEmail, {});

return (
<form className={className} action={formAction} noValidate>
<div className="space-y-2.5">
<Input
id="email"
type="email"
name="email"
autoComplete="username"
required
label="メールアドレス"
errorMessage={state.errors?.email?.[0]}
/>
<Input
id="current-password"
name="password"
label="パスワード"
type="password"
autoComplete="current-password"
required
errorMessage={state.errors?.password?.[0]}
/>
</div>
{state.errors?.base && (
<p className="mt-1 p-1 text-tiny text-danger">{state.errors.base}</p>
)}
<Button className="mt-2.5" fullWidth color="primary" type="submit">
ログイン
</Button>
</form>
);
}
33 changes: 33 additions & 0 deletions app/(auth)/(routes)/login/(components)/SocialProviders/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client";

import classNames from "classnames";
import { Button } from "@/components/Button";
import { GoogleIcon } from "@/components/SocialProviderIcon";
import { signInWithGoogle } from "../LoginForm/actions";

export function SocialProviders({ className }: { className?: string }) {
return (
<ul className={classNames("space-y-2", className)}>
<li className="w-full">
<Button
fullWidth
className="flex items-center justify-center gap-3"
variant="bordered"
onClick={() => signInWithGoogle()}
>
<GoogleIcon className="w-5" />
<span>Google でログイン</span>
</Button>
</li>
<li className="w-full">
<Button
fullWidth
variant="bordered"
onClick={() => alert("Coming soon!")} // TODO: 実装
>
<span>ログインせずに始める</span>
</Button>
</li>
</ul>
);
}
30 changes: 30 additions & 0 deletions app/(auth)/(routes)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Metadata } from "next";
import Link from "next/link";
import { Divider } from "@/components/Divider";
import { LoginForm } from "./(components)/LoginForm";
import { SocialProviders } from "./(components)/SocialProviders";

export const metadata: Metadata = {
title: "ログイン",
};

export default function LoginPage() {
return (
<>
<h1 className="mx-auto mb-4 w-fit text-large font-bold">ログイン</h1>
<SocialProviders />
<div className="my-4 flex items-center gap-4">
<Divider className="shrink" />
<span>or</span>
<Divider className="shrink" />
</div>
<LoginForm />
<p className="mt-4 text-center text-small">
アカウントをお持ちでない方は
<Link className="link" href="/sign-up">
新規登録
</Link>
</p>
</>
);
}
Loading

0 comments on commit b32a047

Please sign in to comment.