diff --git a/application/client/babel.config.js b/application/client/babel.config.js index c3c574591a..bd221f6b52 100644 --- a/application/client/babel.config.js +++ b/application/client/babel.config.js @@ -4,16 +4,23 @@ module.exports = { [ "@babel/preset-env", { - targets: "ie 11", - corejs: "3", + targets: {"chrome": "145"}, + // corejs: "3", modules: "commonjs", useBuiltIns: false, }, + // { + // targets: "ie 11", + // corejs: "3", + // modules: "commonjs", + // useBuiltIns: false, + // }, ], [ "@babel/preset-react", { - development: true, + development: false, + // development: true, runtime: "automatic", }, ], diff --git a/application/client/package.json b/application/client/package.json index 9f8e80a6a8..f7edeaee5f 100644 --- a/application/client/package.json +++ b/application/client/package.json @@ -5,7 +5,7 @@ "license": "MPL-2.0", "author": "CyberAgent, Inc.", "scripts": { - "build": "NODE_ENV=development webpack", + "build": "NODE_ENV=production webpack", "typecheck": "tsc" }, "dependencies": { @@ -20,6 +20,7 @@ "classnames": "2.5.1", "common-tags": "1.8.2", "core-js": "3.45.1", + "dayjs": "1.11.20", "encoding-japanese": "2.2.0", "fast-average-color": "9.5.0", "gifler": "github:themadcreator/gifler#v0.3.0", diff --git a/application/client/src/auth/validation.ts b/application/client/src/auth/validation.ts index 2a83bbfb15..72fe70b0c4 100644 --- a/application/client/src/auth/validation.ts +++ b/application/client/src/auth/validation.ts @@ -13,7 +13,10 @@ export const validate = (values: AuthFormData): FormErrors => { errors.name = "名前を入力してください"; } - if (/^(?:[^\P{Letter}&&\P{Number}]*){16,}$/v.test(normalizedPassword)) { + // if (/^(?:[^\P{Letter}&&\P{Number}]*){16,}$/v.test(normalizedPassword)) { + // errors.password = "パスワードには記号を含める必要があります"; + // } + if (/^(?:[^\P{Letter}&&\P{Number}]*)$/v.test(normalizedPassword)) { errors.password = "パスワードには記号を含める必要があります"; } if (normalizedPassword.length === 0) { diff --git a/application/client/src/components/application/NavigationItem.tsx b/application/client/src/components/application/NavigationItem.tsx index 57e64b004a..2a33157c0e 100644 --- a/application/client/src/components/application/NavigationItem.tsx +++ b/application/client/src/components/application/NavigationItem.tsx @@ -1,7 +1,7 @@ import classNames from "classnames"; -import { useLocation } from "react-router"; +import { Link, useLocation } from "react-router"; -import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link"; +// import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link"; interface Props { badge?: React.ReactNode; diff --git a/application/client/src/components/auth_modal/AuthModalPage.tsx b/application/client/src/components/auth_modal/AuthModalPage.tsx index 08996f9afd..74300ef204 100644 --- a/application/client/src/components/auth_modal/AuthModalPage.tsx +++ b/application/client/src/components/auth_modal/AuthModalPage.tsx @@ -4,9 +4,10 @@ import { Field, formValueSelector, InjectedFormProps, reduxForm } from "redux-fo import { AuthFormData } from "@web-speed-hackathon-2026/client/src/auth/types"; import { validate } from "@web-speed-hackathon-2026/client/src/auth/validation"; import { FormInputField } from "@web-speed-hackathon-2026/client/src/components/foundation/FormInputField"; -import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link"; +// import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link"; import { ModalErrorMessage } from "@web-speed-hackathon-2026/client/src/components/modal/ModalErrorMessage"; import { ModalSubmitButton } from "@web-speed-hackathon-2026/client/src/components/modal/ModalSubmitButton"; +import { Link } from "react-router"; interface Props { onRequestCloseModal: () => void; diff --git a/application/client/src/components/direct_message/DirectMessageListPage.tsx b/application/client/src/components/direct_message/DirectMessageListPage.tsx index 5a373e918e..62b199012f 100644 --- a/application/client/src/components/direct_message/DirectMessageListPage.tsx +++ b/application/client/src/components/direct_message/DirectMessageListPage.tsx @@ -1,12 +1,15 @@ -import moment from "moment"; -import { useCallback, useEffect, useState } from "react"; +// import moment from "moment"; +import 'dayjs/locale/ja'; +import dayjs, { locale, extend } from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime';import { useCallback, useEffect, useState } from "react"; import { Button } from "@web-speed-hackathon-2026/client/src/components/foundation/Button"; import { FontAwesomeIcon } from "@web-speed-hackathon-2026/client/src/components/foundation/FontAwesomeIcon"; -import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link"; +// import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link"; import { useWs } from "@web-speed-hackathon-2026/client/src/hooks/use_ws"; import { fetchJSON } from "@web-speed-hackathon-2026/client/src/utils/fetchers"; import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path"; +import { Link } from 'react-router'; interface Props { activeUser: Models.User; @@ -45,6 +48,9 @@ export const DirectMessageListPage = ({ activeUser, newDmModalId }: Props) => { return null; } + locale('ja'); + extend(relativeTime); + return (
@@ -100,7 +106,8 @@ export const DirectMessageListPage = ({ activeUser, newDmModalId }: Props) => { className="text-cax-text-subtle text-xs" dateTime={lastMessage.createdAt} > - {moment(lastMessage.createdAt).locale("ja").fromNow()} + {/* {moment(lastMessage.createdAt).locale("ja").fromNow()} */} + {dayjs(lastMessage.createdAt).locale("ja").fromNow()} )} diff --git a/application/client/src/components/direct_message/DirectMessagePage.tsx b/application/client/src/components/direct_message/DirectMessagePage.tsx index 098c7d2894..355b89c02c 100644 --- a/application/client/src/components/direct_message/DirectMessagePage.tsx +++ b/application/client/src/components/direct_message/DirectMessagePage.tsx @@ -1,5 +1,5 @@ import classNames from "classnames"; -import moment from "moment"; +// import moment from "moment"; import { ChangeEvent, useCallback, @@ -14,6 +14,7 @@ import { import { FontAwesomeIcon } from "@web-speed-hackathon-2026/client/src/components/foundation/FontAwesomeIcon"; import { DirectMessageFormData } from "@web-speed-hackathon-2026/client/src/direct_message/types"; import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path"; +import dayjs from "dayjs"; interface Props { conversationError: Error | null; @@ -44,13 +45,17 @@ export const DirectMessagePage = ({ const textAreaRows = Math.min((text || "").split("\n").length, 5); const isInvalid = text.trim().length === 0; const scrollHeightRef = useRef(0); + const [isTyping, setIsTyping] = useState(false); const handleChange = useCallback( (event: ChangeEvent) => { setText(event.target.value); - onTyping(); + if(!isTyping) { + onTyping(); + setIsTyping(true) + } }, - [onTyping], + [isTyping], ); const handleKeyDown = useCallback( @@ -80,7 +85,8 @@ export const DirectMessagePage = ({ scrollHeightRef.current = height; window.scrollTo(0, height); } - }, 1); + setIsTyping(false); + }, 5000); return () => clearInterval(id); }, []); @@ -141,7 +147,8 @@ export const DirectMessagePage = ({

{isActiveUserSend && message.isRead && ( 既読 diff --git a/application/client/src/components/foundation/SoundWaveSVG.tsx b/application/client/src/components/foundation/SoundWaveSVG.tsx index d95e63164c..af129db990 100644 --- a/application/client/src/components/foundation/SoundWaveSVG.tsx +++ b/application/client/src/components/foundation/SoundWaveSVG.tsx @@ -1,8 +1,8 @@ -import _ from "lodash"; +import { chunk, map, mean, zip, max } from 'es-toolkit/compat'; import { useEffect, useRef, useState } from "react"; interface ParsedData { - max: number; + maxNum: number; peaks: number[]; } @@ -12,20 +12,20 @@ async function calculate(data: ArrayBuffer): Promise { // 音声をデコードする const buffer = await audioCtx.decodeAudioData(data.slice(0)); // 左の音声データの絶対値を取る - const leftData = _.map(buffer.getChannelData(0), Math.abs); + const leftData = map(buffer.getChannelData(0), Math.abs); // 右の音声データの絶対値を取る - const rightData = _.map(buffer.getChannelData(1), Math.abs); + const rightData = map(buffer.getChannelData(1), Math.abs); // 左右の音声データの平均を取る - const normalized = _.map(_.zip(leftData, rightData), _.mean); + const normalized = map(zip(leftData, rightData), mean); // 100 個の chunk に分ける - const chunks = _.chunk(normalized, Math.ceil(normalized.length / 100)); + const chunks = chunk(normalized, Math.ceil(normalized.length / 100)); // chunk ごとに平均を取る - const peaks = _.map(chunks, _.mean); + const peaks = map(chunks, mean); // chunk の平均の中から最大値を取る - const max = _.max(peaks) ?? 0; + const maxNum = max(peaks) ?? 0; - return { max, peaks }; + return { maxNum, peaks }; } interface Props { @@ -34,21 +34,21 @@ interface Props { export const SoundWaveSVG = ({ soundData }: Props) => { const uniqueIdRef = useRef(Math.random().toString(16)); - const [{ max, peaks }, setPeaks] = useState({ - max: 0, + const [{ maxNum, peaks }, setPeaks] = useState({ + maxNum: 0, peaks: [], }); useEffect(() => { - calculate(soundData).then(({ max, peaks }) => { - setPeaks({ max, peaks }); + calculate(soundData).then(({ maxNum, peaks }) => { + setPeaks({ maxNum, peaks }); }); }, [soundData]); return ( {peaks.map((peak, idx) => { - const ratio = peak / max; + const ratio = peak / maxNum; return ( {

-

diff --git a/application/client/src/components/post/PostItem.tsx b/application/client/src/components/post/PostItem.tsx index 5fa904c91a..e1103ae78e 100644 --- a/application/client/src/components/post/PostItem.tsx +++ b/application/client/src/components/post/PostItem.tsx @@ -1,11 +1,13 @@ -import moment from "moment"; +// import moment from "moment"; -import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link"; +// import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link"; import { ImageArea } from "@web-speed-hackathon-2026/client/src/components/post/ImageArea"; import { MovieArea } from "@web-speed-hackathon-2026/client/src/components/post/MovieArea"; import { SoundArea } from "@web-speed-hackathon-2026/client/src/components/post/SoundArea"; import { TranslatableText } from "@web-speed-hackathon-2026/client/src/components/post/TranslatableText"; import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path"; +import dayjs from "dayjs"; +import { Link } from "react-router"; interface Props { post: Models.Post; @@ -67,8 +69,11 @@ export const PostItem = ({ post }: Props) => { ) : null}

-

diff --git a/application/client/src/components/timeline/TimelineItem.tsx b/application/client/src/components/timeline/TimelineItem.tsx index 21b88980f8..1b8757ac3e 100644 --- a/application/client/src/components/timeline/TimelineItem.tsx +++ b/application/client/src/components/timeline/TimelineItem.tsx @@ -1,4 +1,4 @@ -import moment from "moment"; +// import moment from "moment"; import { MouseEventHandler, useCallback } from "react"; import { Link, useNavigate } from "react-router"; @@ -7,6 +7,7 @@ import { MovieArea } from "@web-speed-hackathon-2026/client/src/components/post/ import { SoundArea } from "@web-speed-hackathon-2026/client/src/components/post/SoundArea"; import { TranslatableText } from "@web-speed-hackathon-2026/client/src/components/post/TranslatableText"; import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path"; +import dayjs from "dayjs"; const isClickedAnchorOrButton = (target: EventTarget | null, currentTarget: Element): boolean => { while (target !== null && target instanceof Element) { @@ -76,8 +77,11 @@ export const TimelineItem = ({ post }: Props) => { - -