Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"dev:ini": "npx prisma migrate deploy & npx prisma db seed & next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint": "eslint .",
"format": "eslint . --fix",
"prisma:generate": "npx prisma generate",
"prisma:dev": "npx prisma migrate dev",
"prisma:deploy": "npx prisma migrate deploy",
Expand Down
34 changes: 34 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,40 @@
}
}

/* SlimeButton animations */
@keyframes drop-drift-1 {
0% {
transform: translate(-4px, 0);
}
50% {
transform: translate(2px, 2px);
}
100% {
transform: translate(-4px, 0);
}
}

@keyframes drop-drift-2 {
0% {
transform: translate(4px, -1px);
}
50% {
transform: translate(-2px, 1px);
}
100% {
transform: translate(4px, -1px);
}
}

/* Accessibility: Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
}

body {
background: var(--background);
color: var(--foreground);
Expand Down
20 changes: 20 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased font-mono`}
>
{/* SVG defs for gooey effects (SlimeButton) */}
<svg width='0' height='0' className='absolute' aria-hidden>
<defs>
<filter id='goo'>
<feGaussianBlur
in='SourceGraphic'
stdDeviation='6'
result='blur'
/>
<feColorMatrix
in='blur'
mode='matrix'
values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7'
result='goo'
/>
<feBlend in='SourceGraphic' in2='goo' />
</filter>
</defs>
</svg>

<Suspense fallback={null}>
<FirebaseAnalytics />
</Suspense>
Expand Down
180 changes: 92 additions & 88 deletions src/app/public/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import { useMemo, useState } from "react"
import { useRouter } from "next/navigation"
import { ButtonBase } from "@/components/atoms/ButtonBase"
import { LogIn, UserX } from "lucide-react"
import { SlimeButton } from "@/components/atoms/SlimeButton"
import {
handleGoogleLoginByFirebase,
handleGuestLoginByFirebase,
Expand Down Expand Up @@ -43,90 +44,93 @@ export default function LoginPage() {

return (
<div className='w-full max-w-md bg-white shadow-xl rounded-2xl p-8'>
<h1 className='text-3xl font-bold text-center text-primary mb-6'>
ログイン
</h1>

<form onSubmit={onSubmit} className='space-y-4'>
<div>
<label
htmlFor='email'
className='block text-sm font-medium text-text mb-1'
>
メールアドレス
</label>
<input
type='email'
id='email'
required
className={joincn(
"w-full px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary",
emailError ? "border-red-500" : "",
<h1 className='text-3xl font-bold text-center text-primary mb-6'>
ログイン
</h1>

<form onSubmit={onSubmit} className='space-y-4'>
<div>
<label
htmlFor='email'
className='block text-sm font-medium text-text mb-1'
>
メールアドレス
</label>
<input
type='email'
id='email'
required
className={joincn(
"w-full px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary",
emailError ? "border-red-500" : "",
)}
value={email}
onChange={(e) => onChangeEmail(e.target.value)}
disabled={isLoading}
/>
{emailError && (
<p className='text-red-500 text-sm mt-1'>{emailError}</p>
)}
value={email}
onChange={(e) => onChangeEmail(e.target.value)}
disabled={isLoading}
/>
{emailError && (
<p className='text-red-500 text-sm mt-1'>{emailError}</p>
)}
</div>
</div>

<div>
<label
htmlFor='password'
className='block text-sm font-medium text-text mb-1'
>
パスワード
</label>
<input
type='password'
id='password'
required
className={joincn(
"w-full px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary",
passwordError ? "border-red-500" : "",
<div>
<label
htmlFor='password'
className='block text-sm font-medium text-text mb-1'
>
パスワード
</label>
<input
type='password'
id='password'
required
className={joincn(
"w-full px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary",
passwordError ? "border-red-500" : "",
)}
value={password}
onChange={(e) => onChangePassword(e.target.value)}
disabled={isLoading}
/>
{passwordError && (
<p className='text-red-500 text-sm mt-1'>{passwordError}</p>
)}
value={password}
onChange={(e) => onChangePassword(e.target.value)}
disabled={isLoading}
/>
{passwordError && (
<p className='text-red-500 text-sm mt-1'>{passwordError}</p>
</div>

{isLoading ? (
<Skeleton className='w-full h-10 rounded-xl' />
) : (
<SlimeButton
type='submit'
colorMode='primary'
sizeMode='full'
disabled={isLoading}
>
<LogIn className='size-4' aria-hidden />
{isLoading ? "ログイン中..." : "ログイン"}
</SlimeButton>
)}
</form>

<div className='mt-6 text-center text-sm text-gray-500'>または</div>

<div className='mt-4'>
{isLoading ? (
<Skeleton className='w-full h-10 rounded-xl' />
) : (
<SlimeButton
colorMode='ghost'
sizeMode='full'
onClick={() => login("GUEST")}
disabled={isLoading}
>
<UserX className='size-4' aria-hidden />
{isLoading ? "ログイン中..." : "ゲストでログイン"}
</SlimeButton>
)}
</div>

{isLoading ? (
<Skeleton className='w-full h-10 rounded-xl' />
) : (
<ButtonBase
type='submit'
sizeMode='full'
className='font-semibold'
disabled={isLoading}
>
{isLoading ? "ログイン中..." : "ログイン"}
</ButtonBase>
)}
</form>

<div className='mt-6 text-center text-sm text-gray-500'>または</div>

{isLoading ? (
<Skeleton className='w-full h-10 rounded-xl' />
) : (
<ButtonBase
colorMode='ghost'
sizeMode='full'
className='mt-4'
onClick={() => login("GUEST")}
disabled={isLoading}
>
{isLoading ? "ログイン中..." : "ゲストでログイン"}
</ButtonBase>
)}

{/* <ButtonBase
{/* <ButtonBase
colorMode='outline'
sizeMode='full'
className='mt-4'
Expand All @@ -136,15 +140,15 @@ export default function LoginPage() {
Googleでログイン
</ButtonBase> */}

<p className='mt-6 text-center text-sm text-gray-500'>
アカウントをお持ちでない方は{" "}
<a
href='/public/signup'
className='text-primary font-medium hover:underline'
>
サインアップ
</a>
</p>
<p className='mt-6 text-center text-sm text-gray-500'>
アカウントをお持ちでない方は{" "}
<a
href='/public/signup'
className='text-primary font-medium hover:underline'
>
サインアップ
</a>
</p>

{/* ToDo 退会コード入力処理はあとで実装 */}
{/* <Dialog open={currentPhase === "input-quit-code"}>
Expand Down
27 changes: 11 additions & 16 deletions src/app/v1/user/exercise/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client"

import { useEffect, useMemo, useState } from "react"
import { useSearchParams, useRouter } from "next/navigation"
import { ButtonBase } from "@/components/atoms/ButtonBase"
import { InfoArea } from "@/components/atoms/InfoArea"
import { PopCard } from "@/components/atoms/PopCard"
import { SectionTitle } from "@/components/atoms/SectionTitle"
import { BackButton } from "@/components/molecules/BackButton"
import { Dialog, DialogContent, DialogHeader } from "@/components/ui/dialog"
Expand All @@ -10,13 +12,11 @@ import { decodeBase64ForUrl } from "@/lib/functions/decodeBase64"
import { encodeBase64ForUrl } from "@/lib/functions/encodeBase64"
import { joincn } from "@/lib/functions/joincn"
import { DialogTitle } from "@radix-ui/react-dialog"
import { useSearchParams, useRouter } from "next/navigation"
import { useEffect, useMemo, useState } from "react"

export default function Page() {
const {
dataToGetUserExercise,
dataTooGetRecommendExercises,
dataToGetRecommendExercises,
startExercise,
openExerciseInfoDialog,
onOpenExerciseInfoDialog,
Expand All @@ -33,24 +33,19 @@ export default function Page() {
<SectionTitle title='おすすめの問題集' />

<section className='mb-6 flex md:flex-row flex-col gap-x-3 gap-y-3'>
{dataTooGetRecommendExercises && dataTooGetRecommendExercises.success
? dataTooGetRecommendExercises.data.recommendExercises.map(
{dataToGetRecommendExercises && dataToGetRecommendExercises.success
? dataToGetRecommendExercises.data.recommendExercises.map(
(exercise) => (
<InfoArea
<PopCard
key={encodeBase64ForUrl(exercise.id)}
className={joincn(
`bg-background-subtle rounded-2xl shadow h-[160px]`,
`hover:shadow-lg transition`,
`cursor-pointer`,
"md:basis-1/3 sm:w-full",
)}
className={joincn(`h-[160px]`)}
onClick={() => {
onOpenExerciseInfoDialog(exercise.id)
}}
>
<h3 className='text-lg font-bold mb-2'>{exercise.title}</h3>
<p className='text-sm'>{exercise.description}</p>
</InfoArea>
</PopCard>
),
)
: null}
Expand Down Expand Up @@ -108,7 +103,7 @@ function usePage() {

const [exerciseId, setExerciseId] = useState<string | null>(null)

const { dataTooGetRecommendExercises } = useGetRecommendExercises()
const { dataToGetRecommendExercises } = useGetRecommendExercises()
const { dataToGetUserExercise } = useGetUserExercise(exerciseId || "")

useEffect(() => {
Expand Down Expand Up @@ -146,7 +141,7 @@ function usePage() {

return {
dataToGetUserExercise,
dataTooGetRecommendExercises,
dataToGetRecommendExercises,
startExercise,
openExerciseInfoDialog,
onOpenExerciseInfoDialog,
Expand Down
Loading
Loading