+
-
- {userData?.name}
-
-
-
- {userData?.streak}日連続
-
-
- 達成率{successRate}%
-
-
- 累計{userData?.completed}回達成
+
+
+ {userData?.name}
+
+
+ {userData?.streak}日連続
+
+
+ 達成率{successRate}%
+
+
+ 累計{userData?.completed}回達成
+
+
-
-
- ({
- gap: "0px",
- "--Stepper-verticalGap": "30px",
- "--StepIndicator-size": "2.5rem",
- "--Step-gap": "1rem",
- "--Step-connectorInset": "0.5rem",
- "--Step-connectorRadius": "1rem",
- "--Step-connectorThickness": "4px",
- "--joy-palette-success-solidBg": "var(--joy-palette-success-400)",
- [`& .${stepClasses.completed}`]: {
- "&::after": { bgcolor: "success.solidBg" },
- },
- // copletedとactive両方の場合
- [`& .${stepClasses.completed}.${stepClasses.active}`]: {
- [`& .${stepIndicatorClasses.root}`]: {
- border: "4px solid #fff",
- boxShadow: `0 0 0 1px ${theme.vars.palette.primary[500]}`,
+
+ ({
+ gap: "0px",
+ "--Stepper-verticalGap": "30px",
+ "--StepIndicator-size": "2.5rem",
+ "--Step-gap": "1rem",
+ "--Step-connectorInset": "0.5rem",
+ "--Step-connectorRadius": "1rem",
+ "--Step-connectorThickness": "4px",
+ "--joy-palette-success-solidBg": "var(--joy-palette-success-400)",
+ [`& .${stepClasses.completed}`]: {
+ "&::after": { bgcolor: "success.solidBg" },
},
- "&::after": {
- bgcolor: "success.solidBg",
- marginTop: "-50px",
+ // copletedとactive両方の場合
+ [`& .${stepClasses.completed}.${stepClasses.active}`]: {
+ [`& .${stepIndicatorClasses.root}`]: {
+ border: "4px solid #fff",
+ boxShadow: `0 0 0 1px ${theme.vars.palette.primary[500]}`,
+ },
+ "&::after": {
+ bgcolor: "success.solidBg",
+ marginTop: "-50px",
+ },
},
- },
- [`& .${stepClasses.active}`]: {
- [`& .${stepIndicatorClasses.root}`]: {
- border: "4px solid #fff",
- boxShadow: `0 0 0 1px ${theme.vars.palette.primary[500]}`,
+ [`& .${stepClasses.active}`]: {
+ [`& .${stepIndicatorClasses.root}`]: {
+ border: "4px solid #fff",
+ boxShadow: `0 0 0 1px ${theme.vars.palette.primary[500]}`,
+ },
},
- },
- [`& .${stepClasses.disabled} *`]: {
- color: "neutral.softDisabledColor",
- },
- [`& .${typographyClasses["title-sm"]}`]: {
- textTransform: "uppercase",
- letterSpacing: "1px",
- fontSize: "10px",
- },
- })}
- >
- {children}
-
-
+ [`& .${stepClasses.disabled} *`]: {
+ color: "neutral.softDisabledColor",
+ },
+ [`& .${typographyClasses["title-sm"]}`]: {
+ textTransform: "uppercase",
+ letterSpacing: "1px",
+ fontSize: "10px",
+ },
+ })}
+ >
+ {children}
+
+
+
);
};
diff --git a/src/app/discover/page.tsx b/src/app/discover/page.tsx
new file mode 100644
index 0000000..0ddbbad
--- /dev/null
+++ b/src/app/discover/page.tsx
@@ -0,0 +1,12 @@
+"use client";
+import DashBoard from "@/Components/DashBoard/DashBoard";
+import GoalModalButton from "@/Components/GoalModal/GoalModalButton";
+
+export default function Discover() {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 7b4b895..2f82703 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,4 +1,3 @@
-import Header from "@/Components/Header/Header";
import { Loader } from "@/Components/Loader/Loader";
import NavigationMenu from "@/Components/NavigationMenu/NavigationMenu";
import SnackBar from "@/Components/SnackBar/SnackBar";
@@ -8,9 +7,11 @@ import { UserProvider } from "@/utils/UserContext";
import type { Metadata } from "next";
import "./firebase";
+const description = "TODO REALはTODOリストとBeRealを組み合わせたアプリです。";
+
export const metadata: Metadata = {
- title: "Todo Real(仮)",
- description: "BeRealとTodoアプリを組み合わせたアプリ",
+ title: "Todo Real",
+ description,
};
export const viewport = {
@@ -23,11 +24,35 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
+ const rootURL = "https://todo-real-c28fa.web.app/";
+
return (
-
+
+
+ {/* Open Graph */}
+
+
+
+
+
+ {/* Twitter Card */}
+
+
+
+
+
+ {/* Google Fonts */}
+
+
+
-
{children}
diff --git a/src/app/page.module.scss b/src/app/page.module.scss
new file mode 100644
index 0000000..72761cf
--- /dev/null
+++ b/src/app/page.module.scss
@@ -0,0 +1,53 @@
+.introductionBody {
+ --primary-color: #008edc;
+ --primary-font-family: Roboto, "Zen Kaku Gothic New", sans-serif;
+
+ color: var(--primary-color);
+ font-family: var(--primary-font-family);
+ line-height: 1.7;
+
+ --joy-palette-text-primary: var(--primary-color);
+ --joy-fontFamily-display: var(--primary-font-family);
+
+ > div {
+ padding: 15px 5px;
+ }
+
+ > div:nth-child(odd) {
+ background-color: #e8f7ff;
+ }
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ line-height: 1.2;
+ margin: 5px 0;
+ text-align: center;
+ }
+
+ img {
+ border-radius: 13px;
+ display: block;
+ margin: 5px auto;
+ object-fit: contain;
+ width: 95%;
+ }
+
+ .svg {
+ fill: var(--primary-color);
+ height: 35px;
+ margin: 0;
+ padding-bottom: 3px;
+ width: 35px;
+ }
+}
+
+.highlight {
+ background-color: var(--primary-color);
+ color: white;
+ margin: 0 2px;
+ padding: 1px 3px 0;
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 2460720..f84938c 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,12 +1,202 @@
"use client";
-import DashBoard from "@/Components/DashBoard/DashBoard";
-import GoalModalButton from "@/Components/GoalModal/GoalModalButton";
+
+import CenterIn from "@/Components/Animation/CenterIn";
+import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
+import GitHubIcon from "@mui/icons-material/GitHub";
+import Button from "@mui/joy/Button";
+import Typography, { TypographyProps } from "@mui/joy/Typography";
+import { ReactNode } from "react";
+import styled from "styled-components";
+import styles from "./page.module.scss";
+
+const StyledTypography = styled(Typography)`
+ color: var(--primary-color);
+ font-family: Roboto, "Zen Kaku Gothic New", sans-serif;
+ font-weight: 600;
+ padding: 0 3px;
+`;
export default function Top() {
return (
- <>
-
-
- >
+
+
+
+ 友達と共有・競争できるTODOリスト
+
+
+
+ TODO REAL
+
+
+
+
+
+ TODO REALとは?
+
+ やりたいことがつい後回しになってしまったり、毎日続けたいと思っていた習慣が途切れてしまったり…そんな経験はありませんか?
+
+ TODO REALはあなたの目標達成・継続を手助けします!
+
+ TODO REALでは目標を完了したら写真を投稿することができ、
+
+ SNS感覚でTODOリストを使ったり友達と共有できます!
+
+
+ 面倒なイメージのTODOリストも、TODO REALを使えば
+ 思わず開きたくなるアプリに
+ 変えることができます!
+
+
+
+
+
+ 完了したTODOリストを友達と共有
+
+
+ 完了したTODOリストは友達に公開されます。友達に共有することで、達成感を共有できます。
+
+
+
+
+
+
+ 失敗したら友達に晒す
+
+
+ 失敗してもペナルティの無いTODOリストとは異なり、期限内に完了しなかったTODOリストも公開されます。なんて恥ずかしい。
+
+
+
+
+
+
+ 期限が近づくと通知を送信
+
+
+ 5分前になると通知を送信します。通知を受け取ることで、TODOリストの達成を手助けします。
+ {/* TODO: 画像を差し替える */}
+
+
+
+
+
+
+ 毎日継続して友達と競おう
+
+
+ 連続記録で毎日目標を達成することを促進します!
+
+ また、達成率や達成回数で友達と競うこともできます!
+
+
+
+
+
+
+ 目標を作成する面倒を無くす
+
+
+ 毎回目標を設定するのが面倒?ワンクリックで作成可能に!
+
+ 完了投稿をすると投稿ボタンが変化して同じ目標をワンクリックで複製できるように!
+
+
+
+
+
+
+ 友達の投稿を見たいなら、あなたも投稿しましょう
+
+
+ 友達の投稿を表示するには、あなたの完了した目標をシェアしましょう。あなたが最後に投稿した時間より後の目標の画像はモザイクがかかっています。
+
+ {/* TODO: 正式に実装したら画像を新しくする */}
+
+
+
+
+
+ 使い方
+
+
+ 1. 目標を作成
+
+
+ 画面右下の+ボタンから目標を作成できます。
+
+
+
+
+ 2. 投稿
+
+
+ 目標を完了したら写真をアップロードして共有しましょう!
+
+
+
+
+ アップロードしたら自動で友達に公開されます!
+
+
+
+
+
+ 今すぐ始めよう!
+
+ あなたの最初の目標はこのアプリを使い始めることです。
+
+
+
+
+
+ ゲストログインを使用するとアカウントを作成せずに閲覧できますが、投稿機能は使用できません。
+
+
+
+
+
+ 今後追加したい機能
+
+
+ いいねやコメント等の友達とインタラクトできる機能を実装予定です。
+
+
+
+
+
+
+ コード・実装方法はGitHubのドキュメントを参照
+
+
+
+
);
}
+
+const Highlight = ({ children }: { children: ReactNode }) => {
+ return {children};
+};
diff --git a/src/types/types.ts b/src/types/types.ts
index c7fe0df..c1fed72 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -1,3 +1,5 @@
+import { ReactNode } from "react";
+
export interface User {
userId: string;
name: string;
@@ -33,3 +35,20 @@ export interface Post {
export interface PostWithGoalId extends Post {
goalId: string;
}
+
+export const animationTypes = [
+ "left",
+ "right",
+ "center",
+ "bottom",
+ "top",
+] as const;
+export type AnimationType = (typeof animationTypes)[number];
+
+export interface AnimationConfigs {
+ children: ReactNode;
+ duration?: number;
+ delay?: number;
+ distance?: number; // アニメーションの移動距離(ex. 右に100px移動しながらフェードイン)
+ margin?: number; // 画面に入ってから何px超えたら表示するか
+}
diff --git a/src/utils/API/Goal/createGoal.ts b/src/utils/API/Goal/createGoal.ts
index 259f7a0..2817671 100644
--- a/src/utils/API/Goal/createGoal.ts
+++ b/src/utils/API/Goal/createGoal.ts
@@ -8,6 +8,16 @@ import { Goal } from "@/types/types";
* @return {*}
*/
export const createGoal = async (postData: Goal) => {
+ // 過去の時間が入力されている場合
+ if (new Date(postData.deadline).getTime() < Date.now()) {
+ throw new Error("past deadline can't be set");
+ }
+
+ // 文字数制限を100文字までにする
+ if (postData.text.length > 100) {
+ throw new Error("too long comment");
+ }
+
const response = await fetch(`${functionsEndpoint}/goal/`, {
method: "POST",
headers: {
@@ -36,7 +46,12 @@ export const handleCreateGoalError = (error: unknown) => {
let snackBarMessage = "目標の作成に失敗しました";
if (error instanceof Error) {
- console.error("Fetch error:", error.message);
+ if (error.message.includes("past deadline can't be set")) {
+ snackBarMessage = "過去の時間を指定することはできません";
+ }
+ if (error.message.includes("too long comment")) {
+ snackBarMessage = "目標の文字数は100文字以下にしてください";
+ }
if (error.message.includes("400")) {
snackBarMessage = "入力内容に問題があります";
}
diff --git a/src/utils/API/Post/createPost.ts b/src/utils/API/Post/createPost.ts
index 158e9d1..2d0b168 100644
--- a/src/utils/API/Post/createPost.ts
+++ b/src/utils/API/Post/createPost.ts
@@ -8,6 +8,11 @@ import { PostWithGoalId } from "@/types/types";
* @return {*}
*/
export const createPost = async (postData: PostWithGoalId) => {
+ // 文字は100文字まで
+ if (postData.text.length > 100) {
+ throw new Error("too long comment");
+ }
+
const response = await fetch(`${functionsEndpoint}/post/`, {
method: "POST",
headers: {
@@ -36,7 +41,9 @@ export const handleCreatePostError = (error: unknown) => {
let snackBarMessage = "投稿の作成に失敗しました";
if (error instanceof Error) {
- console.error("Fetch error:", error.message);
+ if (error.message.includes("too long comment")) {
+ snackBarMessage = "投稿コメントは100文字以下にしてください";
+ }
if (error.message.includes("400")) {
snackBarMessage = "入力内容に問題があります";
}
diff --git a/src/utils/API/Result/fetchResult.ts b/src/utils/API/Result/fetchResult.ts
index 247bc69..67483a9 100644
--- a/src/utils/API/Result/fetchResult.ts
+++ b/src/utils/API/Result/fetchResult.ts
@@ -56,7 +56,6 @@ export const handleFetchResultError = (error: unknown) => {
let snackBarMessage = "データの取得に失敗しました";
if (error instanceof Error) {
- console.error("Fetch error:", error.message);
if (error.message.includes("404")) {
snackBarMessage = "データが見つかりませんでした";
}
diff --git a/src/utils/API/User/createUser.ts b/src/utils/API/User/createUser.ts
index 08e434f..0f57cc5 100644
--- a/src/utils/API/User/createUser.ts
+++ b/src/utils/API/User/createUser.ts
@@ -22,7 +22,4 @@ export const createUser = async (name: string, userId: string) => {
const data = await response.json();
throw new Error(`Error ${status}: ${data.message}`);
}
-
- const data = await response.json();
- console.log("Success:", data);
};
diff --git a/src/utils/API/User/fetchUser.ts b/src/utils/API/User/fetchUser.ts
index d061cf2..577f38a 100644
--- a/src/utils/API/User/fetchUser.ts
+++ b/src/utils/API/User/fetchUser.ts
@@ -36,7 +36,6 @@ export const handleFetchUserError = (error: unknown) => {
let snackBarMessage = "初回ログインかユーザーデータが見つかりません";
if (error instanceof Error) {
- console.error("Fetch error:", error.message);
if (error.message.includes("404")) {
snackBarMessage = "ユーザー情報が登録されていません";
}
diff --git a/src/utils/UserContext.tsx b/src/utils/UserContext.tsx
index 25f628f..3311b60 100644
--- a/src/utils/UserContext.tsx
+++ b/src/utils/UserContext.tsx
@@ -68,7 +68,7 @@ export const UserProvider = ({ children }: Props) => {
auth,
async (firebaseUser: FirebaseUser | null) => {
if (!firebaseUser) {
- console.log("No user is logged in.");
+ console.log("ログインしていません");
setUser(null);
return;
}