@potenlab/ui 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +361 -0
- package/dist/cli.js +756 -0
- package/package.json +8 -5
- package/template/admin/README.md +36 -0
- package/template/admin/_gitignore +41 -0
- package/template/admin/components.json +23 -0
- package/template/admin/docs/changes.json +295 -0
- package/template/admin/docs/dev-plan.md +822 -0
- package/template/admin/docs/frontend-plan.md +874 -0
- package/template/admin/docs/prd.md +408 -0
- package/template/admin/docs/progress.json +777 -0
- package/template/admin/docs/test-plan.md +790 -0
- package/template/admin/docs/ui-ux-plan.md +1664 -0
- package/template/admin/eslint.config.mjs +18 -0
- package/template/admin/next.config.ts +7 -0
- package/template/admin/package.json +43 -0
- package/template/admin/postcss.config.mjs +7 -0
- package/template/admin/public/avatars/user1.svg +4 -0
- package/template/admin/public/avatars/user2.svg +4 -0
- package/template/admin/public/avatars/user3.svg +4 -0
- package/template/admin/public/avatars/user4.svg +4 -0
- package/template/admin/public/avatars/user5.svg +4 -0
- package/template/admin/public/file.svg +1 -0
- package/template/admin/public/globe.svg +1 -0
- package/template/admin/public/next.svg +1 -0
- package/template/admin/public/profile/img1.svg +7 -0
- package/template/admin/public/profile/img2.svg +7 -0
- package/template/admin/public/profile/img3.svg +7 -0
- package/template/admin/public/vercel.svg +1 -0
- package/template/admin/public/window.svg +1 -0
- package/template/admin/src/app/favicon.ico +0 -0
- package/template/admin/src/app/layout.tsx +38 -0
- package/template/admin/src/app/page.tsx +5 -0
- package/template/admin/src/app/users/[id]/page.tsx +10 -0
- package/template/admin/src/components/layouts/app-sidebar.tsx +152 -0
- package/template/admin/src/components/user-management/profile-images.tsx +69 -0
- package/template/admin/src/components/user-management/user-detail-form.tsx +143 -0
- package/template/admin/src/features/user-management/components/user-columns.tsx +101 -0
- package/template/admin/src/features/user-management/components/user-detail.tsx +79 -0
- package/template/admin/src/features/user-management/components/user-list.tsx +74 -0
- package/template/admin/src/features/user-management/types/index.ts +113 -0
- package/template/admin/src/features/user-management/utils/format.ts +2 -0
- package/template/admin/src/lib/mock-data.ts +131 -0
- package/template/admin/src/styles/globals.css +26 -0
- package/template/admin/tsconfig.json +34 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ColumnDef } from "@tanstack/react-table";
|
|
4
|
+
import { Avatar, AvatarImage, AvatarFallback, Button, DataTableColumnHeader } from "@potenlab/ui";
|
|
5
|
+
import type { User } from "@/features/user-management/types";
|
|
6
|
+
|
|
7
|
+
interface GetUserColumnsOptions {
|
|
8
|
+
onDelete?: (user: User) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getUserColumns(
|
|
12
|
+
options?: GetUserColumnsOptions
|
|
13
|
+
): ColumnDef<User>[] {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
accessorKey: "nickname",
|
|
17
|
+
header: "닉네임",
|
|
18
|
+
meta: { className: "flex-1" },
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
accessorKey: "grade",
|
|
22
|
+
header: "등급",
|
|
23
|
+
meta: { className: "flex-1" },
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
accessorKey: "avatar",
|
|
27
|
+
header: "아바타",
|
|
28
|
+
meta: { className: "w-[80px] text-center", headerClassName: "w-[80px] text-center" },
|
|
29
|
+
cell: ({ row }) => (
|
|
30
|
+
<div className="flex items-center justify-center">
|
|
31
|
+
<Avatar>
|
|
32
|
+
<AvatarImage
|
|
33
|
+
src={row.original.avatar}
|
|
34
|
+
alt={`${row.original.nickname} avatar`}
|
|
35
|
+
/>
|
|
36
|
+
<AvatarFallback>{row.original.nickname.charAt(0)}</AvatarFallback>
|
|
37
|
+
</Avatar>
|
|
38
|
+
</div>
|
|
39
|
+
),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
accessorKey: "phone",
|
|
43
|
+
header: "휴대폰 번호",
|
|
44
|
+
meta: { className: "flex-1" },
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
accessorKey: "age",
|
|
48
|
+
header: "나이",
|
|
49
|
+
meta: { className: "flex-1" },
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
accessorKey: "gender",
|
|
53
|
+
header: "성별",
|
|
54
|
+
meta: { className: "flex-1" },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
accessorKey: "region",
|
|
58
|
+
header: "지역",
|
|
59
|
+
meta: { className: "flex-1" },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
accessorKey: "joinDate",
|
|
63
|
+
header: ({ column }) => (
|
|
64
|
+
<DataTableColumnHeader column={column} title="가입일" />
|
|
65
|
+
),
|
|
66
|
+
meta: { className: "flex-1" },
|
|
67
|
+
enableSorting: true,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
accessorKey: "withdrawalDate",
|
|
71
|
+
header: ({ column }) => (
|
|
72
|
+
<DataTableColumnHeader column={column} title="탈퇴일" />
|
|
73
|
+
),
|
|
74
|
+
meta: { className: "flex-1" },
|
|
75
|
+
enableSorting: true,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "delete",
|
|
79
|
+
header: "삭제",
|
|
80
|
+
meta: { className: "w-[57px]" },
|
|
81
|
+
cell: ({ row }) => (
|
|
82
|
+
<Button
|
|
83
|
+
variant="ghost"
|
|
84
|
+
size="ghost"
|
|
85
|
+
onClick={(e) => {
|
|
86
|
+
e.stopPropagation();
|
|
87
|
+
options?.onDelete?.(row.original);
|
|
88
|
+
}}
|
|
89
|
+
onKeyDown={(e) => {
|
|
90
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
91
|
+
e.stopPropagation();
|
|
92
|
+
}
|
|
93
|
+
}}
|
|
94
|
+
aria-label={`Delete user ${row.original.nickname}`}
|
|
95
|
+
>
|
|
96
|
+
삭제
|
|
97
|
+
</Button>
|
|
98
|
+
),
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useForm } from "react-hook-form";
|
|
5
|
+
import { Pencil } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
import { Card, CardContent, PageHeader } from "@potenlab/ui";
|
|
8
|
+
import { UserDetailForm } from "@/components/user-management/user-detail-form";
|
|
9
|
+
import { mockUsers } from "@/lib/mock-data";
|
|
10
|
+
import type { UserDetailFormValues } from "@/features/user-management/types";
|
|
11
|
+
|
|
12
|
+
export interface UserDetailProps {
|
|
13
|
+
userId: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function UserDetail({ userId }: UserDetailProps) {
|
|
17
|
+
const user = mockUsers.find((u) => u.id === userId) || mockUsers[0];
|
|
18
|
+
|
|
19
|
+
const [profileImages, setProfileImages] = useState<string[]>(user.profileImages);
|
|
20
|
+
|
|
21
|
+
const form = useForm<UserDetailFormValues>({
|
|
22
|
+
defaultValues: {
|
|
23
|
+
role: user.role,
|
|
24
|
+
nickname: user.nickname,
|
|
25
|
+
phone: user.phone,
|
|
26
|
+
age: user.age,
|
|
27
|
+
gender: user.gender,
|
|
28
|
+
exerciseStyle: user.exerciseStyle,
|
|
29
|
+
gymRelocation: user.gymRelocation,
|
|
30
|
+
region: user.region,
|
|
31
|
+
bench: user.bench,
|
|
32
|
+
deadlift: user.deadlift,
|
|
33
|
+
squat: user.squat,
|
|
34
|
+
intro: user.intro,
|
|
35
|
+
profilePublic: user.settings.profilePublic,
|
|
36
|
+
matchChatNotification: user.settings.matchChatNotification,
|
|
37
|
+
marketingNotification: user.settings.marketingNotification,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const handleImageUpload = (index: number, file: File) => {
|
|
42
|
+
const previewUrl = URL.createObjectURL(file);
|
|
43
|
+
setProfileImages((prev) => {
|
|
44
|
+
const updated = [...prev];
|
|
45
|
+
if (index < updated.length) {
|
|
46
|
+
updated[index] = previewUrl;
|
|
47
|
+
} else {
|
|
48
|
+
updated.push(previewUrl);
|
|
49
|
+
}
|
|
50
|
+
return updated;
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleSave = form.handleSubmit((data) => {
|
|
55
|
+
// TODO: implement save logic
|
|
56
|
+
console.log("Save:", data);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Card>
|
|
61
|
+
<CardContent className="p-8">
|
|
62
|
+
<PageHeader
|
|
63
|
+
title="사용자관리"
|
|
64
|
+
subtitle="사용자 정보를 수정할 수 있습니다."
|
|
65
|
+
actionLabel="변경사항 저장"
|
|
66
|
+
actionIcon={Pencil}
|
|
67
|
+
onAction={handleSave}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<UserDetailForm
|
|
71
|
+
user={user}
|
|
72
|
+
form={form}
|
|
73
|
+
profileImages={profileImages}
|
|
74
|
+
onImageUpload={handleImageUpload}
|
|
75
|
+
/>
|
|
76
|
+
</CardContent>
|
|
77
|
+
</Card>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { Pencil } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
import { Card, CardContent, PageHeader, TabNavigation, SearchBar, PaginationControls, DataTable } from "@potenlab/ui";
|
|
8
|
+
import { getUserColumns } from "@/features/user-management/components/user-columns";
|
|
9
|
+
import { mockUsers, totalUserCount } from "@/lib/mock-data";
|
|
10
|
+
|
|
11
|
+
export function UserList() {
|
|
12
|
+
const router = useRouter();
|
|
13
|
+
const [searchValue, setSearchValue] = useState("");
|
|
14
|
+
const [activeTab, setActiveTab] = useState("all");
|
|
15
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
16
|
+
const [itemsPerPage, setItemsPerPage] = useState(10);
|
|
17
|
+
|
|
18
|
+
const columns = useMemo(
|
|
19
|
+
() =>
|
|
20
|
+
getUserColumns({
|
|
21
|
+
onDelete: (user) => {
|
|
22
|
+
// TODO: implement delete confirmation dialog
|
|
23
|
+
console.log("Delete user:", user.id);
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
[]
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Card>
|
|
31
|
+
<CardContent className="p-8">
|
|
32
|
+
<div className="flex flex-col gap-6">
|
|
33
|
+
<PageHeader
|
|
34
|
+
title="사용자관리"
|
|
35
|
+
subtitle="사용자 리스트 관리 페이지"
|
|
36
|
+
badgeCount={totalUserCount}
|
|
37
|
+
actionLabel="작성"
|
|
38
|
+
actionIcon={Pencil}
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
<TabNavigation
|
|
42
|
+
tabs={[
|
|
43
|
+
{ value: "all", label: "전체" },
|
|
44
|
+
{ value: "tab2", label: "Tab" },
|
|
45
|
+
{ value: "tab3", label: "Tab" },
|
|
46
|
+
]}
|
|
47
|
+
defaultValue={activeTab}
|
|
48
|
+
onTabChange={setActiveTab}
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
<SearchBar
|
|
52
|
+
value={searchValue}
|
|
53
|
+
onChange={setSearchValue}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
<PaginationControls
|
|
57
|
+
currentPage={currentPage}
|
|
58
|
+
totalPages={1}
|
|
59
|
+
itemsPerPage={itemsPerPage}
|
|
60
|
+
onPageChange={setCurrentPage}
|
|
61
|
+
onItemsPerPageChange={setItemsPerPage}
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
<DataTable
|
|
65
|
+
columns={columns}
|
|
66
|
+
data={mockUsers}
|
|
67
|
+
enableSorting
|
|
68
|
+
onRowClick={(user) => router.push(`/users/${user.id}`)}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</CardContent>
|
|
72
|
+
</Card>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/features/user-management/types/index.ts
|
|
2
|
+
|
|
3
|
+
export interface UserSettings {
|
|
4
|
+
profilePublic: boolean;
|
|
5
|
+
matchChatNotification: boolean;
|
|
6
|
+
marketingNotification: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface User {
|
|
10
|
+
id: string;
|
|
11
|
+
nickname: string;
|
|
12
|
+
grade: string;
|
|
13
|
+
avatar: string;
|
|
14
|
+
phone: string;
|
|
15
|
+
age: string;
|
|
16
|
+
gender: string;
|
|
17
|
+
region: string;
|
|
18
|
+
joinDate: string;
|
|
19
|
+
withdrawalDate: string;
|
|
20
|
+
role: string;
|
|
21
|
+
exerciseStyle: string;
|
|
22
|
+
gymRelocation: string;
|
|
23
|
+
bench: string;
|
|
24
|
+
deadlift: string;
|
|
25
|
+
squat: string;
|
|
26
|
+
intro: string;
|
|
27
|
+
profileImages: string[];
|
|
28
|
+
settings: UserSettings;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface UserDetailFormValues {
|
|
32
|
+
role: string;
|
|
33
|
+
nickname: string;
|
|
34
|
+
phone: string;
|
|
35
|
+
age: string;
|
|
36
|
+
gender: string;
|
|
37
|
+
exerciseStyle: string;
|
|
38
|
+
gymRelocation: string;
|
|
39
|
+
region: string;
|
|
40
|
+
bench: string;
|
|
41
|
+
deadlift: string;
|
|
42
|
+
squat: string;
|
|
43
|
+
intro: string;
|
|
44
|
+
profilePublic: boolean;
|
|
45
|
+
matchChatNotification: boolean;
|
|
46
|
+
marketingNotification: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @deprecated Use ColumnDef<User> from @tanstack/react-table with getUserColumns() instead */
|
|
50
|
+
export interface UserTableColumn {
|
|
51
|
+
key: keyof User | "delete";
|
|
52
|
+
label: string;
|
|
53
|
+
width: string; // CSS width: "flex-1", "80px", "57px"
|
|
54
|
+
sortable: boolean;
|
|
55
|
+
alignment: "left" | "center";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @deprecated Use getUserColumns() from @/features/user-management/components/user-columns instead */
|
|
59
|
+
export const USER_TABLE_COLUMNS: UserTableColumn[] = [
|
|
60
|
+
{ key: "nickname", label: "닉네임", width: "flex-1", sortable: false, alignment: "left" },
|
|
61
|
+
{ key: "grade", label: "등급", width: "flex-1", sortable: false, alignment: "left" },
|
|
62
|
+
{ key: "avatar", label: "아바타", width: "80px", sortable: false, alignment: "center" },
|
|
63
|
+
{ key: "phone", label: "휴대폰 번호", width: "flex-1", sortable: false, alignment: "left" },
|
|
64
|
+
{ key: "age", label: "나이", width: "flex-1", sortable: false, alignment: "left" },
|
|
65
|
+
{ key: "gender", label: "성별", width: "flex-1", sortable: false, alignment: "left" },
|
|
66
|
+
{ key: "region", label: "지역", width: "flex-1", sortable: false, alignment: "left" },
|
|
67
|
+
{ key: "joinDate", label: "가입일", width: "flex-1", sortable: true, alignment: "left" },
|
|
68
|
+
{ key: "withdrawalDate", label: "탈퇴일", width: "flex-1", sortable: true, alignment: "left" },
|
|
69
|
+
{ key: "delete", label: "삭제", width: "57px", sortable: false, alignment: "left" },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export interface PaginationState {
|
|
73
|
+
currentPage: number;
|
|
74
|
+
totalPages: number;
|
|
75
|
+
itemsPerPage: number;
|
|
76
|
+
pageJumpInput: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const DEFAULT_PAGINATION: PaginationState = {
|
|
80
|
+
currentPage: 1,
|
|
81
|
+
totalPages: 1,
|
|
82
|
+
itemsPerPage: 10,
|
|
83
|
+
pageJumpInput: "",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type FormFieldType = "input" | "select";
|
|
87
|
+
|
|
88
|
+
export interface FormField {
|
|
89
|
+
key: keyof User;
|
|
90
|
+
label: string;
|
|
91
|
+
type: FormFieldType;
|
|
92
|
+
options?: string[]; // Only for type "select"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const BASIC_INFO_ROW_1: FormField[] = [
|
|
96
|
+
{ key: "role", label: "역할", type: "select", options: ["사용자", "관리자"] },
|
|
97
|
+
{ key: "nickname", label: "닉네임", type: "input" },
|
|
98
|
+
{ key: "phone", label: "휴대폰", type: "input" },
|
|
99
|
+
{ key: "age", label: "나이", type: "input" },
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
export const BASIC_INFO_ROW_2: FormField[] = [
|
|
103
|
+
{ key: "gender", label: "성별", type: "select", options: ["남자", "여자"] },
|
|
104
|
+
{ key: "exerciseStyle", label: "운동 스타일", type: "select", options: ["보디빌딩", "크로스핏", "유산소"] },
|
|
105
|
+
{ key: "gymRelocation", label: "헬스장 이전", type: "select", options: ["가능", "불가능"] },
|
|
106
|
+
{ key: "region", label: "지역", type: "select", options: ["서울 마포구", "강남구", "송파구"] },
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
export const BASIC_INFO_ROW_3: FormField[] = [
|
|
110
|
+
{ key: "bench", label: "벤치프레스", type: "input" },
|
|
111
|
+
{ key: "deadlift", label: "데드리프트", type: "input" },
|
|
112
|
+
{ key: "squat", label: "스쿼트", type: "input" },
|
|
113
|
+
];
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { User } from "@/features/user-management/types";
|
|
2
|
+
|
|
3
|
+
export const mockUsers: User[] = [
|
|
4
|
+
{
|
|
5
|
+
id: "1",
|
|
6
|
+
nickname: "닉네임입니다.",
|
|
7
|
+
grade: "매니아",
|
|
8
|
+
avatar: "/avatars/user1.svg",
|
|
9
|
+
phone: "010-1234-1234",
|
|
10
|
+
age: "1999년생",
|
|
11
|
+
gender: "남자",
|
|
12
|
+
region: "강남구",
|
|
13
|
+
joinDate: "2022년 11월 1일",
|
|
14
|
+
withdrawalDate: "2022년 11월 1일",
|
|
15
|
+
role: "사용자",
|
|
16
|
+
exerciseStyle: "보디빌딩",
|
|
17
|
+
gymRelocation: "가능",
|
|
18
|
+
bench: "100kg",
|
|
19
|
+
deadlift: "100kg",
|
|
20
|
+
squat: "100kg",
|
|
21
|
+
intro: "한줄 소개 내용입니다.",
|
|
22
|
+
profileImages: ["/profile/img1.svg", "/profile/img2.svg", "/profile/img3.svg"],
|
|
23
|
+
settings: {
|
|
24
|
+
profilePublic: false,
|
|
25
|
+
matchChatNotification: true,
|
|
26
|
+
marketingNotification: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "2",
|
|
31
|
+
nickname: "닉네임입니다.",
|
|
32
|
+
grade: "매니아",
|
|
33
|
+
avatar: "/avatars/user2.svg",
|
|
34
|
+
phone: "010-1234-1234",
|
|
35
|
+
age: "1999년생",
|
|
36
|
+
gender: "남자",
|
|
37
|
+
region: "강남구",
|
|
38
|
+
joinDate: "2022년 11월 1일",
|
|
39
|
+
withdrawalDate: "2022년 11월 1일",
|
|
40
|
+
role: "사용자",
|
|
41
|
+
exerciseStyle: "보디빌딩",
|
|
42
|
+
gymRelocation: "가능",
|
|
43
|
+
bench: "100kg",
|
|
44
|
+
deadlift: "100kg",
|
|
45
|
+
squat: "100kg",
|
|
46
|
+
intro: "한줄 소개 내용입니다.",
|
|
47
|
+
profileImages: ["/profile/img1.svg", "/profile/img2.svg", "/profile/img3.svg"],
|
|
48
|
+
settings: {
|
|
49
|
+
profilePublic: false,
|
|
50
|
+
matchChatNotification: true,
|
|
51
|
+
marketingNotification: false,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "3",
|
|
56
|
+
nickname: "닉네임입니다.",
|
|
57
|
+
grade: "매니아",
|
|
58
|
+
avatar: "/avatars/user3.svg",
|
|
59
|
+
phone: "010-1234-1234",
|
|
60
|
+
age: "1999년생",
|
|
61
|
+
gender: "남자",
|
|
62
|
+
region: "강남구",
|
|
63
|
+
joinDate: "2022년 11월 1일",
|
|
64
|
+
withdrawalDate: "2022년 11월 1일",
|
|
65
|
+
role: "사용자",
|
|
66
|
+
exerciseStyle: "보디빌딩",
|
|
67
|
+
gymRelocation: "가능",
|
|
68
|
+
bench: "100kg",
|
|
69
|
+
deadlift: "100kg",
|
|
70
|
+
squat: "100kg",
|
|
71
|
+
intro: "한줄 소개 내용입니다.",
|
|
72
|
+
profileImages: ["/profile/img1.svg", "/profile/img2.svg", "/profile/img3.svg"],
|
|
73
|
+
settings: {
|
|
74
|
+
profilePublic: false,
|
|
75
|
+
matchChatNotification: true,
|
|
76
|
+
marketingNotification: false,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "4",
|
|
81
|
+
nickname: "닉네임입니다.",
|
|
82
|
+
grade: "매니아",
|
|
83
|
+
avatar: "/avatars/user4.svg",
|
|
84
|
+
phone: "010-1234-1234",
|
|
85
|
+
age: "1999년생",
|
|
86
|
+
gender: "남자",
|
|
87
|
+
region: "강남구",
|
|
88
|
+
joinDate: "2022년 11월 1일",
|
|
89
|
+
withdrawalDate: "2022년 11월 1일",
|
|
90
|
+
role: "사용자",
|
|
91
|
+
exerciseStyle: "보디빌딩",
|
|
92
|
+
gymRelocation: "가능",
|
|
93
|
+
bench: "100kg",
|
|
94
|
+
deadlift: "100kg",
|
|
95
|
+
squat: "100kg",
|
|
96
|
+
intro: "한줄 소개 내용입니다.",
|
|
97
|
+
profileImages: ["/profile/img1.svg", "/profile/img2.svg", "/profile/img3.svg"],
|
|
98
|
+
settings: {
|
|
99
|
+
profilePublic: false,
|
|
100
|
+
matchChatNotification: true,
|
|
101
|
+
marketingNotification: false,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "5",
|
|
106
|
+
nickname: "닉네임입니다.",
|
|
107
|
+
grade: "매니아",
|
|
108
|
+
avatar: "/avatars/user5.svg",
|
|
109
|
+
phone: "010-1234-1234",
|
|
110
|
+
age: "1999년생",
|
|
111
|
+
gender: "남자",
|
|
112
|
+
region: "강남구",
|
|
113
|
+
joinDate: "2022년 11월 1일",
|
|
114
|
+
withdrawalDate: "2022년 11월 1일",
|
|
115
|
+
role: "사용자",
|
|
116
|
+
exerciseStyle: "보디빌딩",
|
|
117
|
+
gymRelocation: "가능",
|
|
118
|
+
bench: "100kg",
|
|
119
|
+
deadlift: "100kg",
|
|
120
|
+
squat: "100kg",
|
|
121
|
+
intro: "한줄 소개 내용입니다.",
|
|
122
|
+
profileImages: ["/profile/img1.svg", "/profile/img2.svg", "/profile/img3.svg"],
|
|
123
|
+
settings: {
|
|
124
|
+
profilePublic: false,
|
|
125
|
+
matchChatNotification: true,
|
|
126
|
+
marketingNotification: false,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
export const totalUserCount = 100000;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "shadcn/tailwind.css";
|
|
4
|
+
@import "@potenlab/ui/styles/globals.css";
|
|
5
|
+
@source "../node_modules/@potenlab/ui/dist";
|
|
6
|
+
|
|
7
|
+
@layer base {
|
|
8
|
+
* {
|
|
9
|
+
@apply border-border outline-ring/50;
|
|
10
|
+
}
|
|
11
|
+
body {
|
|
12
|
+
@apply bg-background text-foreground;
|
|
13
|
+
background-color: #FCFCFC;
|
|
14
|
+
font-family: 'Pretendard Variable', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@media (prefers-reduced-motion: reduce) {
|
|
19
|
+
*,
|
|
20
|
+
*::before,
|
|
21
|
+
*::after {
|
|
22
|
+
animation-duration: 0.01ms !important;
|
|
23
|
+
animation-iteration-count: 1 !important;
|
|
24
|
+
transition-duration: 0.01ms !important;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|