Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

타이머 배경 #93

Open
wants to merge 7 commits into
base: feat/theme-image-preview
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion app/admin/(components)/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default function Sidebar(props: Props) {
`/admin?themeId=${encodeURIComponent(selectedThemeId)}
`
);
}, [selectedThemeId]);
}, [selectedThemeId, params]);

const navigateToNewTheme = () => {
resetSelectedTheme();
Expand Down
2 changes: 1 addition & 1 deletion app/admin/(components)/ThemeInfo/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ThemeDrawer from "../ThemeDrawer/Container";

import ThemeInfoTitle from "./ThemeInfoTitle";
import ThemeInfoHint from "./ThemeInfoHint";
import ThemeImage from "./ThemeImage";
import ThemeImage from "./ThemeTimerImage";

export default function ThemeInfo() {
const { open } = useModal();
Expand Down
4 changes: 2 additions & 2 deletions app/admin/(components)/ThemeInfo/ThemeImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ export default function ThemeImage() {
const handleAddImageBtnClick = () => {
// 숨겨진 input 클릭 트리거
// imgInputRef.current?.click();
open(Dialog, { type: "put" });
open(Dialog);
};
const handlePreviewImageBtnClick = () => {
// 숨겨진 input 클릭 트리거
// imgInputRef.current?.click();
open(PreviewDialog, { type: "put" });
open(PreviewDialog);
};
const [isHovered, setIsHovered] = useState(false);

Expand Down
98 changes: 98 additions & 0 deletions app/admin/(components)/ThemeInfo/ThemeTimerImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import Image from "next/image";
import React, { ChangeEvent, useRef } from "react";

import Dialog from "@/components/common/Dialog-new/Image-Dialog-new/Dialog";
import PreviewDialog from "@/components/common/Dialog-new/Preview-Dialog-new/PreviewDialog";
import useModal from "@/hooks/useModal";
import { useTimerImageWrite } from "@/components/atoms/timerImage.atom";
import { useSelectedThemeValue } from "@/components/atoms/selectedTheme.atom";
import { defaultTimerImage, QuestionIconProps } from "@/admin/(consts)/sidebar";
import DeleteDialog from "@/components/common/Dialog-new/Timer-Image-Delete-Dialog/DeleteDialog";

export default function ThemeTimerImage() {
const selectedTheme = useSelectedThemeValue();

const isTimerImage = selectedTheme.useTimerUrl;
const setTimerImage = useTimerImageWrite();

const TimerImageProps = {
src: isTimerImage ? selectedTheme.themeImageUrl! : defaultTimerImage,
alt: "NEXT ROOM",
width: 120,
height: 120,
};
const { open } = useModal();

const addImageInputRef = useRef<HTMLInputElement>(null);
const fileReset = () => {
if (addImageInputRef.current) {
addImageInputRef.current.value = "";
}
};

const handleFileInputChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) {
return;
}
const file: File = e.target.files[0];
setTimerImage({ timerImage: file });

if (file) {
open(Dialog);
}
fileReset();
};
const handleAddTimerImageBtnClick = () => {
addImageInputRef.current?.click();
};
const handlePreviewBtnClick = () => {
open(PreviewDialog);
};

const handleDelTimerImageBtnClick = () => {
open(DeleteDialog);
};

return (
<div className="theme_image__container">
<div className="theme-image-title">
<span>타이머 배경</span>
<Image {...QuestionIconProps} />
</div>
<div className="theme-images">
<div className="theme-image-box">
<Image {...TimerImageProps} />
{isTimerImage && (
<div
className="theme-image-dimmed"
onClick={handleDelTimerImageBtnClick}
>
<button className="button28" type="button">
삭제
</button>
</div>
)}
</div>
<input
type="file"
onChange={handleFileInputChange}
accept="image/*"
style={{ display: "none" }}
ref={addImageInputRef}
/>
{isTimerImage ? (
<button
className="secondary_button40"
onClick={handlePreviewBtnClick}
>
미리보기
</button>
) : (
<button className="button40" onClick={handleAddTimerImageBtnClick}>
배경 등록하기
</button>
)}
</div>
</div>
);
}
30 changes: 16 additions & 14 deletions app/admin/(consts)/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,6 @@ export const smallXProps = {
height: 16,
};

export const previewProps = {
src: "/images/svg/image.png",
alt: "x icon",
width: 315,
height: 682,
};

export const statusBarProps = {
src: "/images/svg/status_bar.svg",
alt: "status_bar",
width: 315,
height: 40,
};

export const timerPreviewProps = {
src: "/images/svg/timer_preview.svg",
alt: "timer_preview",
Expand All @@ -71,6 +57,22 @@ export const settingProps = {
height: 24,
};

export const QuestionIconProps = {
src: "/images/svg/icon_question.svg",
alt: "gallery_image",
width: 24,
height: 24,
};

export const timerPreviewLineProps = {
src: "/images/svg/timer_preview_entire.svg",
alt: "TIMER_LINE_IMAGE",
width: 158,
height: 340,
};

export const defaultTimerImage = "/images/svg/icon_preview.svg";
export const defaultTimerImagePreview = "/images/svg/timer_preview.svg";
export const timerTooltipProps = {
src: "/images/png/tooltip.png",
alt: "tooltip",
Expand Down
1 change: 0 additions & 1 deletion app/admin/(style)/admin.modules.sass
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@
width: 100vw
height: 100vh
background-color: $color-black60
z-index: 100
.modal-1
position: fixed
left: 0
Expand Down
5 changes: 4 additions & 1 deletion app/admin/(style)/themeInfo.modules.sass
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
@include title24SB
color: $color-white
position: relative

div
cursor: default

.drawer-open
width: calc(100% - 520px)
Expand All @@ -40,6 +41,7 @@
position: absolute
right: 0
top: 30px
cursor: pointer

.theme-infomation-text
@include body14M
Expand Down Expand Up @@ -324,6 +326,7 @@
span
@include title16SB
margin-right: 2px
cursor: default
img
vertical-align: bottom

Expand Down
11 changes: 6 additions & 5 deletions app/admin/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ type Theme = {

function Admin() {
const { data: categories = [], isLoading } = useGetThemeList();

const isLoggedIn = useCheckSignIn();

const [selectedTheme, setSelectedTheme] = useSelectedTheme();
Expand All @@ -30,6 +29,12 @@ function Admin() {
const [toast, setToast] = useToastInfo();
const router = useRouter();

useEffect(() => {
if (!isLoading && categories.length > 0 && selectedTheme.id === 0) {
setSelectedTheme(categories[categories.length - 1]);
}
}, [isLoading]);

const handleClickSelected = (theme: Theme) => {
setSelectedTheme(theme);
setSelectedThemeId(theme.id);
Expand Down Expand Up @@ -57,10 +62,6 @@ function Admin() {
isOpen: toast.isOpen,
};

if (!isLoggedIn || isLoading) {
return <Loader />;
}

return <AdminView {...SidebarViewProps} />;
}

Expand Down
2 changes: 2 additions & 0 deletions app/components/atoms/selectedTheme.atom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface SelectedTheme {
title: string;
timeLimit: number;
hintLimit: number;
themeImageUrl?: string;
useTimerUrl?: boolean;
}

export const InitialSelectedTheme: SelectedTheme = {
Expand Down
18 changes: 18 additions & 0 deletions app/components/atoms/timerImage.atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
atom,
useRecoilValue,
useRecoilState,
useSetRecoilState,
} from "recoil";

interface TimerImageType {
timerImage: File | undefined;
}
const timerImage = atom<TimerImageType>({
key: "timerImage",
default: { timerImage: undefined },
});

export const useTimerImage = () => useRecoilState(timerImage);
export const useTimerImageValue = () => useRecoilValue(timerImage);
export const useTimerImageWrite = () => useSetRecoilState(timerImage);
92 changes: 26 additions & 66 deletions app/components/common/Dialog-new/Image-Dialog-new/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,103 +1,63 @@
import React, { forwardRef, useRef } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
import React, { FormEvent, forwardRef, useRef } from "react";
import Image from "next/image";

import { usePutTheme } from "@/mutations/putTheme";
import { useDeleteTheme } from "@/mutations/deleteTheme";
import {
useSelectedTheme,
useSelectedThemeReset,
} from "@/components/atoms/selectedTheme.atom";
import {
useCreateThemeReset,
useCreateThemeValue,
} from "@/components/atoms/createTheme.atom";
import { useSelectedTheme } from "@/components/atoms/selectedTheme.atom";
import useClickOutside from "@/hooks/useClickOutside";
import { deleteProps, xProps } from "@/admin/(consts)/sidebar";
import { xProps } from "@/admin/(consts)/sidebar";
import useModal from "@/hooks/useModal";
import ModalPortal from "@/components/common/Dialog-new/ModalPortal";

import DialogBody from "./DialogBody";

import "@/components/common/Dialog-new/dialog.sass";
import useTimerImageUpload from "@/mutations/useTimerImageUpload";
import { useTimerImageValue } from "@/components/atoms/timerImage.atom";

interface DialogProps {
type?: string | "";
}

interface FormValues {
id: number;
title: string;
timeLimit: number;
hintLimit: number;
}
import DialogBody from "./DialogBody";

const Dialog = forwardRef<HTMLFormElement, DialogProps>((props) => {
const { open, close } = useModal();
const { type = "" } = props;
const Dialog = forwardRef<HTMLFormElement>(() => {
const { close } = useModal();
const formRef = useRef<HTMLFormElement | null>(null);

const handleOpenDeleteModal = (event: React.MouseEvent) => {
event.stopPropagation();
open(Dialog, { type: "delete" });
};
const [selectedTheme] = useSelectedTheme();
const { timerImage } = useTimerImageValue();
const { handleProcess } = useTimerImageUpload();

const { handleSubmit } = useForm<FormValues>();
const [selectedTheme, setSelectedTheme] = useSelectedTheme();
const createTheme = useCreateThemeValue();
const resetCreateTheme = useCreateThemeReset();
const resetSelectedTheme = useSelectedThemeReset();
const isDisabled =
type === "put"
? (String(createTheme.title) === String(selectedTheme.title) &&
Number(createTheme.timeLimit) === Number(selectedTheme.timeLimit) &&
Number(createTheme.hintLimit) === Number(selectedTheme.hintLimit)) ||
!(createTheme.title && createTheme.timeLimit && createTheme.hintLimit)
: !(createTheme.title && createTheme.timeLimit && createTheme.hintLimit);

const { mutateAsync: putTheme } = usePutTheme();
const { mutateAsync: deleteTheme } = useDeleteTheme();

const onSubmit: SubmitHandler<FormValues> = () => {
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
const { id } = selectedTheme;

const submitData = {
...createTheme,
id,
themeId: id,
timerImageFile: timerImage,
};

if (type === "put") {
putTheme(submitData);
setSelectedTheme(submitData);
} else if (type === "delete") {
deleteTheme({ id });
resetSelectedTheme();
try {
await handleProcess(submitData);
} catch (error) {
console.error(error);
}
close();
resetCreateTheme();

return close();
close();
};

useClickOutside(formRef, close);

return (
<ModalPortal>
<form
className={`theme-info-modal ${type}`}
className={`theme-info-modal`}
ref={formRef}
onSubmit={handleSubmit(onSubmit)}
onSubmit={handleSubmit}
onClick={(e) => e.stopPropagation()}
>
<div className="theme-info-modal__header">
<h2>타이머 배경 수정</h2>
<h2>타이머 배경 올리기</h2>
<button className="close-button" type="button" onClick={close}>
<Image {...xProps} />
</button>
</div>
<DialogBody />
<div className="theme-info-modal__footer">
<p className="text">힌트폰에 곧바로 적용됩니다</p>
<p className="timer-preview-image-footer-text">
힌트폰에 곧바로 적용됩니다
</p>
<div className="action-buttons">
<button className="outlined_button40" type="button" onClick={close}>
취소
Expand Down
Loading
Loading