Skip to content

Commit

Permalink
[테마마켓] 등록/다운로드 기능 추가 (#198)
Browse files Browse the repository at this point in the history
  • Loading branch information
ars-ki-00 authored Jan 26, 2025
1 parent 9e31de6 commit ce771c7
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 19 deletions.
54 changes: 51 additions & 3 deletions apps/theme-market/src/app/(routes)/(main)/(bottomsheet)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,68 @@
import { BottomSheet } from "@/app/_components/BottomSheet";
import { ThemeDetail } from "@/app/_pages/Main/BottomSheet/ThemeDetail";
import { useThemeStore } from "@/app/_providers/ThemeProvider";
import { useUserStore } from "@/app/_providers/UserProvider";
import { themeService } from "@/services/ThemeService";
import { useState } from "react";

export default function MainBottomSheet() {
const [isAnonymous, setIsAnonymous] = useState<boolean>(false);

const { theme, setTheme } = useThemeStore((state) => state);
const { user, accessToken } = useUserStore((state) => state);

const updateIsAnonymous = () => {
setIsAnonymous((current) => !current);
};

const publishTheme = () => {
if (!theme) return;

const isConfirm = window.confirm(
`'${theme.name}' 테마를 등록하시겠습니까?`
);

if (isConfirm)
themeService.publishTheme(theme.id, theme.name, isAnonymous, accessToken);
};

const downloadTheme = () => {
if (!theme) return;

const isConfirm = window.confirm(`해당 테마를 다운로드 하시겠습니까?`);

if (isConfirm) themeService.downloadTheme(theme.id, accessToken);
};

const isMyTheme = user.nickname.nickname === theme?.publishInfo.authorName;

const bottomSheetProps = isMyTheme
? {
title: "내 테마 올리기",
confirmText: "등록",
onConfirm: () => publishTheme(),
}
: {
title: "테마 다운로드",
confirmText: "담기",
onConfirm: () => downloadTheme(),
};

return (
<>
<BottomSheet
title={"title"}
isOpen={!!theme}
onCancel={() => setTheme(null)}
{...bottomSheetProps}
>
{theme && <ThemeDetail theme={theme} />}
{theme && (
<ThemeDetail
theme={theme}
isAnonymous={isAnonymous}
updateIsAnonymous={updateIsAnonymous}
/>
)}
</BottomSheet>
{/* )} */}
</>
);
}
9 changes: 8 additions & 1 deletion apps/theme-market/src/app/(routes)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { ReactNode } from "react";
import styles from "./index.module.css";
import { cookieService } from "@/services/CookieService";
import { ThemeStoreProvider } from "@/app/_providers/ThemeProvider";
import { UserStoreProvider } from "../_providers/UserProvider";
import { authService } from "@/services/AuthService";

export const metadata: Metadata = {
title: "SNUTT 테마 마켓",
Expand All @@ -21,11 +23,16 @@ interface Props {

export default async function RootLayout({ children }: Props) {
const themeMode = cookieService.get("theme", "light");
const accessToken = cookieService.getAccessToken();

const user = await authService.me(accessToken);

return (
<html lang="ko" data-theme={themeMode}>
<body className={styles.layout}>
<ThemeStoreProvider>{children}</ThemeStoreProvider>
<UserStoreProvider user={user} accessToken={accessToken!!}>
<ThemeStoreProvider>{children}</ThemeStoreProvider>
</UserStoreProvider>
</body>
</html>
);
Expand Down
13 changes: 11 additions & 2 deletions apps/theme-market/src/app/_components/BottomSheet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@ interface Props {
children?: React.ReactNode;
isOpen: boolean;
title: string;
confirmText: string;
onCancel?: () => void;
onConfirm?: () => void;
}

export const BottomSheet = ({ children, title, isOpen, onCancel }: Props) => {
export const BottomSheet = ({
children,
title,
confirmText,
isOpen,
onCancel,
onConfirm,
}: Props) => {
const router = useRouter();

return (
Expand All @@ -22,7 +31,7 @@ export const BottomSheet = ({ children, title, isOpen, onCancel }: Props) => {
<div className={styles.header}>
<span onClick={() => onCancel?.()}>취소</span>
<span className={styles.title}>{title}</span>
<span>담기</span>
<span onClick={() => onConfirm?.()}>{confirmText}</span>
</div>
<div>{children}</div>
</section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
font-weight: 400;
line-height: 15px;
text-align: left;
color: var(--palette-gray-2);
color: var(--palette-black);

background-color: var(--color-bg-default);
}
Expand All @@ -29,12 +29,13 @@
}

.colors {
margin-top: 16px;
padding: 4px 0px;
margin: 16px 0;
padding: 4px 0px 0px;
background-color: var(--color-bg-default);
}

.colors .info {
color: var(--palette-gray-2);
border-bottom: 0.5px solid var(--palette-gray-10);
}

Expand All @@ -53,3 +54,78 @@
width: 20px;
height: 20px;
}

.anonymous {
width: 100%;
height: 48px;
padding: 0 28px;

display: flex;
justify-content: space-between;
align-items: center;

font-size: 16px;
font-weight: 400;
line-height: 15px;
text-align: left;
color: var(--palette-black);

background-color: var(--color-bg-default);
}

.switch {
position: relative;
display: inline-block;
/* width: 60px;*/
/* height: 34px; */
width: 51px;
height: 31px;
}

.switch input {
opacity: 0;
width: 0;
height: 0;
}

.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: 0.4s;
transition: 0.4s;
}

.slider:before {
position: absolute;
content: "";
height: 27px;
width: 27px;
left: 2px;
bottom: 2px;
background-color: white;
-webkit-transition: 0.4s;
transition: 0.4s;
}

input:checked + .slider {
background-color: #2196f3;
}

input:checked + .slider:before {
-webkit-transform: translateX(20px);
-ms-transform: translateX(20px);
transform: translateX(20px);
}

.slider.round {
border-radius: 34px;
}

.slider.round:before {
border-radius: 50%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
import { Theme } from "@/entities/Theme";
import styles from "./index.module.css";
import { Preview } from "./Preview";
import classNames from "classnames";

interface Props {
theme: Theme;
isAnonymous: boolean;
updateIsAnonymous: () => void;
}

export const ThemeDetail = ({ theme }: Props) => {
export const ThemeDetail = ({
theme,
isAnonymous,
updateIsAnonymous,
}: Props) => {
return (
<section className={styles.wrapper}>
<div className={styles.info}>
Expand All @@ -26,6 +33,19 @@ export const ThemeDetail = ({ theme }: Props) => {
</div>
))}
</div>
{theme.status != "PUBLISHED" && (
<div className={styles.anonymous}>
<span>익명으로 올리기</span>
<label className={styles.switch}>
<input
type="checkbox"
checked={isAnonymous}
onChange={() => updateIsAnonymous()}
/>
<span className={classNames(styles.slider, styles.round)} />
</label>
</div>
)}
<Preview colors={theme.colors} />
</section>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,26 @@ import Image from "next/image";
import styles from "./index.module.css";

import SvgDownload from "@/assets/icons/svgDownload.svg";
import { useRouter } from "next/navigation";
import { Theme } from "@/entities/Theme";
import { useThemeStore } from "@/app/_providers/ThemeProvider";

interface Props {
theme: Theme;
}

export const ThemeInfo = ({ theme }: Props) => {
const router = useRouter();
const { setTheme } = useThemeStore((state) => state);

return (
<article
className={styles.wrapper}
onClick={() => router.push(`/theme/${theme.id}`)}
>
<article className={styles.wrapper} onClick={() => setTheme(theme)}>
<div className={styles.metadata}>
<div className={styles.info}>
<h2 className={styles.title}>{theme.publishInfo.publishName}</h2>
<span className={styles.creator}>{theme.publishInfo.authorName}</span>
</div>
<div className={styles.download}>
<Image src={SvgDownload} alt="download" />
<span>10</span>
<span>{theme.publishInfo.downloads || 0}</span>
</div>
</div>
<div className={styles.colorList}>
Expand Down
4 changes: 3 additions & 1 deletion apps/theme-market/src/app/_providers/UserProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type UserStoreApi = ReturnType<typeof createUserStore>;
export interface UserStoreProviderProps {
children: ReactNode;
user: User;
accessToken: string;
}

export const UserStoreContext = createContext<UserStoreApi | undefined>(
Expand All @@ -19,10 +20,11 @@ export const UserStoreContext = createContext<UserStoreApi | undefined>(
export const UserStoreProvider = ({
children,
user,
accessToken,
}: UserStoreProviderProps) => {
const ref = useRef<UserStoreApi>();
if (!ref.current) {
ref.current = createUserStore({ user });
ref.current = createUserStore({ user, accessToken });
}

return (
Expand Down
1 change: 1 addition & 0 deletions apps/theme-market/src/app/_stores/UserStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createStore } from "zustand";

export type UserStoreState = {
user: User;
accessToken: string;
};

export type UserStoreAction = {};
Expand Down
1 change: 1 addition & 0 deletions apps/theme-market/src/entities/Theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type Theme = {
colors: ThemeColorInfo[];
publishInfo: ThemePublishInfo;
isCustom: Boolean;
status: string;
};

export type ThemeColorInfo = {
Expand Down
28 changes: 28 additions & 0 deletions apps/theme-market/src/repositories/ThemeRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ type ThemeRepository = {
page?: number;
accessToken?: string;
}) => Promise<Theme[]>;
publishTheme: ({
themeId,
publishName,
isAnonymous,
accessToken,
}: {
themeId: string;
publishName: string;
isAnonymous: boolean;
accessToken?: string;
}) => Promise<void>;
downloadTheme: ({
themeId,
accessToken,
}: {
themeId: string;
accessToken: string;
}) => Promise<void>;
};

const DEFAULT_PAGE = 1;
Expand Down Expand Up @@ -64,4 +82,14 @@ export const themeRepositry: ThemeRepository = {

return res.content;
},
publishTheme: async ({ themeId, publishName, isAnonymous, accessToken }) => {
await httpClient.post(
`/v1/themes/${themeId}/publish`,
{ publishName, isAnonymous },
accessToken
);
},
downloadTheme: async ({ themeId, accessToken }) => {
await httpClient.post(`/v1/themes/${themeId}/download`, {}, accessToken);
},
};
1 change: 0 additions & 1 deletion apps/theme-market/src/services/CookieService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ACCESS_TOKEN_KEY } from "@/clients/HttpClient";
import { cookieRepository } from "@/repositories/CookieRepository";
import { cookies } from "next/headers";

type CookieService = {
get: (name: string, defaultValue: string) => string | undefined;
Expand Down
Loading

0 comments on commit ce771c7

Please sign in to comment.