add user profile, profile vibe and trip vibe and social signal

This commit is contained in:
2026-05-08 19:20:27 +07:00
parent 3228ef712f
commit 7f419638b5
39 changed files with 1361 additions and 192 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ export type User = Prisma.UserModel
/**
* Model UserProfile
* Profil sosial publik. Berisi info yang user pilih untuk dibagikan ke peserta lain
* (bio, kota, minat). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
* (bio, kota, minat, vibe). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
*/
export type UserProfile = Prisma.UserProfileModel
/**
+1 -1
View File
@@ -49,7 +49,7 @@ export type User = Prisma.UserModel
/**
* Model UserProfile
* Profil sosial publik. Berisi info yang user pilih untuk dibagikan ke peserta lain
* (bio, kota, minat). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
* (bio, kota, minat, vibe). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
*/
export type UserProfile = Prisma.UserProfileModel
/**
+34
View File
@@ -148,6 +148,23 @@ export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
}
export type EnumVibeNullableFilter<$PrismaModel = never> = {
equals?: $Enums.Vibe | Prisma.EnumVibeFieldRefInput<$PrismaModel> | null
in?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
notIn?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
not?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel> | $Enums.Vibe | null
}
export type EnumVibeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.Vibe | Prisma.EnumVibeFieldRefInput<$PrismaModel> | null
in?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
notIn?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
not?: Prisma.NestedEnumVibeNullableWithAggregatesFilter<$PrismaModel> | $Enums.Vibe | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel>
_max?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel>
}
export type IntNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
@@ -417,6 +434,23 @@ export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
}
export type NestedEnumVibeNullableFilter<$PrismaModel = never> = {
equals?: $Enums.Vibe | Prisma.EnumVibeFieldRefInput<$PrismaModel> | null
in?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
notIn?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
not?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel> | $Enums.Vibe | null
}
export type NestedEnumVibeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.Vibe | Prisma.EnumVibeFieldRefInput<$PrismaModel> | null
in?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
notIn?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
not?: Prisma.NestedEnumVibeNullableWithAggregatesFilter<$PrismaModel> | $Enums.Vibe | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel>
_max?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel>
}
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
+9
View File
@@ -9,6 +9,15 @@
* 🟢 You can import this file directly.
*/
export const Vibe = {
CHILL: 'CHILL',
BALANCED: 'BALANCED',
HARDCORE: 'HARDCORE'
} as const
export type Vibe = (typeof Vibe)[keyof typeof Vibe]
export const VerificationStatus = {
PENDING: 'PENDING',
APPROVED: 'APPROVED',
File diff suppressed because one or more lines are too long
@@ -1065,6 +1065,7 @@ export const UserProfileScalarFieldEnum = {
city: 'city',
interests: 'interests',
instagram: 'instagram',
vibe: 'vibe',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
@@ -1130,6 +1131,7 @@ export const TripScalarFieldEnum = {
endDate: 'endDate',
maxParticipants: 'maxParticipants',
price: 'price',
vibe: 'vibe',
status: 'status',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
@@ -1241,6 +1243,20 @@ export type BooleanFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel,
/**
* Reference to a field of type 'Vibe'
*/
export type EnumVibeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Vibe'>
/**
* Reference to a field of type 'Vibe[]'
*/
export type ListEnumVibeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Vibe[]'>
/**
* Reference to a field of type 'Int'
*/
@@ -100,6 +100,7 @@ export const UserProfileScalarFieldEnum = {
city: 'city',
interests: 'interests',
instagram: 'instagram',
vibe: 'vibe',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
@@ -165,6 +166,7 @@ export const TripScalarFieldEnum = {
endDate: 'endDate',
maxParticipants: 'maxParticipants',
price: 'price',
vibe: 'vibe',
status: 'status',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
+51 -1
View File
@@ -51,6 +51,7 @@ export type TripMinAggregateOutputType = {
endDate: Date | null
maxParticipants: number | null
price: number | null
vibe: $Enums.Vibe | null
status: $Enums.TripStatus | null
createdAt: Date | null
updatedAt: Date | null
@@ -72,6 +73,7 @@ export type TripMaxAggregateOutputType = {
endDate: Date | null
maxParticipants: number | null
price: number | null
vibe: $Enums.Vibe | null
status: $Enums.TripStatus | null
createdAt: Date | null
updatedAt: Date | null
@@ -93,6 +95,7 @@ export type TripCountAggregateOutputType = {
endDate: number
maxParticipants: number
price: number
vibe: number
status: number
createdAt: number
updatedAt: number
@@ -126,6 +129,7 @@ export type TripMinAggregateInputType = {
endDate?: true
maxParticipants?: true
price?: true
vibe?: true
status?: true
createdAt?: true
updatedAt?: true
@@ -147,6 +151,7 @@ export type TripMaxAggregateInputType = {
endDate?: true
maxParticipants?: true
price?: true
vibe?: true
status?: true
createdAt?: true
updatedAt?: true
@@ -168,6 +173,7 @@ export type TripCountAggregateInputType = {
endDate?: true
maxParticipants?: true
price?: true
vibe?: true
status?: true
createdAt?: true
updatedAt?: true
@@ -276,6 +282,7 @@ export type TripGroupByOutputType = {
endDate: Date | null
maxParticipants: number
price: number
vibe: $Enums.Vibe | null
status: $Enums.TripStatus
createdAt: Date
updatedAt: Date
@@ -320,6 +327,7 @@ export type TripWhereInput = {
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
maxParticipants?: Prisma.IntFilter<"Trip"> | number
price?: Prisma.IntFilter<"Trip"> | number
vibe?: Prisma.EnumVibeNullableFilter<"Trip"> | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFilter<"Trip"> | $Enums.TripStatus
createdAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
@@ -345,6 +353,7 @@ export type TripOrderByWithRelationInput = {
endDate?: Prisma.SortOrderInput | Prisma.SortOrder
maxParticipants?: Prisma.SortOrder
price?: Prisma.SortOrder
vibe?: Prisma.SortOrderInput | Prisma.SortOrder
status?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
@@ -373,6 +382,7 @@ export type TripWhereUniqueInput = Prisma.AtLeast<{
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
maxParticipants?: Prisma.IntFilter<"Trip"> | number
price?: Prisma.IntFilter<"Trip"> | number
vibe?: Prisma.EnumVibeNullableFilter<"Trip"> | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFilter<"Trip"> | $Enums.TripStatus
createdAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
@@ -398,6 +408,7 @@ export type TripOrderByWithAggregationInput = {
endDate?: Prisma.SortOrderInput | Prisma.SortOrder
maxParticipants?: Prisma.SortOrder
price?: Prisma.SortOrder
vibe?: Prisma.SortOrderInput | Prisma.SortOrder
status?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
@@ -427,6 +438,7 @@ export type TripScalarWhereWithAggregatesInput = {
endDate?: Prisma.DateTimeNullableWithAggregatesFilter<"Trip"> | Date | string | null
maxParticipants?: Prisma.IntWithAggregatesFilter<"Trip"> | number
price?: Prisma.IntWithAggregatesFilter<"Trip"> | number
vibe?: Prisma.EnumVibeNullableWithAggregatesFilter<"Trip"> | $Enums.Vibe | null
status?: Prisma.EnumTripStatusWithAggregatesFilter<"Trip"> | $Enums.TripStatus
createdAt?: Prisma.DateTimeWithAggregatesFilter<"Trip"> | Date | string
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Trip"> | Date | string
@@ -448,6 +460,7 @@ export type TripCreateInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -472,6 +485,7 @@ export type TripUncheckedCreateInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -496,6 +510,7 @@ export type TripUpdateInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -520,6 +535,7 @@ export type TripUncheckedUpdateInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -544,6 +560,7 @@ export type TripCreateManyInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -565,6 +582,7 @@ export type TripUpdateManyMutationInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -585,6 +603,7 @@ export type TripUncheckedUpdateManyInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -616,6 +635,7 @@ export type TripCountOrderByAggregateInput = {
endDate?: Prisma.SortOrder
maxParticipants?: Prisma.SortOrder
price?: Prisma.SortOrder
vibe?: Prisma.SortOrder
status?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
@@ -642,6 +662,7 @@ export type TripMaxOrderByAggregateInput = {
endDate?: Prisma.SortOrder
maxParticipants?: Prisma.SortOrder
price?: Prisma.SortOrder
vibe?: Prisma.SortOrder
status?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
@@ -663,6 +684,7 @@ export type TripMinOrderByAggregateInput = {
endDate?: Prisma.SortOrder
maxParticipants?: Prisma.SortOrder
price?: Prisma.SortOrder
vibe?: Prisma.SortOrder
status?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
@@ -794,6 +816,7 @@ export type TripCreateWithoutOrganizerInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -817,6 +840,7 @@ export type TripUncheckedCreateWithoutOrganizerInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -869,6 +893,7 @@ export type TripScalarWhereInput = {
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
maxParticipants?: Prisma.IntFilter<"Trip"> | number
price?: Prisma.IntFilter<"Trip"> | number
vibe?: Prisma.EnumVibeNullableFilter<"Trip"> | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFilter<"Trip"> | $Enums.TripStatus
createdAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
@@ -890,6 +915,7 @@ export type TripCreateWithoutReviewsInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -913,6 +939,7 @@ export type TripUncheckedCreateWithoutReviewsInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -952,6 +979,7 @@ export type TripUpdateWithoutReviewsInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -975,6 +1003,7 @@ export type TripUncheckedUpdateWithoutReviewsInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -998,6 +1027,7 @@ export type TripCreateWithoutImagesInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -1021,6 +1051,7 @@ export type TripUncheckedCreateWithoutImagesInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -1060,6 +1091,7 @@ export type TripUpdateWithoutImagesInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -1083,6 +1115,7 @@ export type TripUncheckedUpdateWithoutImagesInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -1106,6 +1139,7 @@ export type TripCreateWithoutParticipantsInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -1129,6 +1163,7 @@ export type TripUncheckedCreateWithoutParticipantsInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -1168,6 +1203,7 @@ export type TripUpdateWithoutParticipantsInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -1191,6 +1227,7 @@ export type TripUncheckedUpdateWithoutParticipantsInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -1214,6 +1251,7 @@ export type TripCreateManyOrganizerInput = {
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
@@ -1234,6 +1272,7 @@ export type TripUpdateWithoutOrganizerInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -1257,6 +1296,7 @@ export type TripUncheckedUpdateWithoutOrganizerInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -1280,6 +1320,7 @@ export type TripUncheckedUpdateManyWithoutOrganizerInput = {
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
@@ -1349,6 +1390,7 @@ export type TripSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
endDate?: boolean
maxParticipants?: boolean
price?: boolean
vibe?: boolean
status?: boolean
createdAt?: boolean
updatedAt?: boolean
@@ -1375,6 +1417,7 @@ export type TripSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensio
endDate?: boolean
maxParticipants?: boolean
price?: boolean
vibe?: boolean
status?: boolean
createdAt?: boolean
updatedAt?: boolean
@@ -1397,6 +1440,7 @@ export type TripSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensio
endDate?: boolean
maxParticipants?: boolean
price?: boolean
vibe?: boolean
status?: boolean
createdAt?: boolean
updatedAt?: boolean
@@ -1419,13 +1463,14 @@ export type TripSelectScalar = {
endDate?: boolean
maxParticipants?: boolean
price?: boolean
vibe?: boolean
status?: boolean
createdAt?: boolean
updatedAt?: boolean
organizerId?: boolean
}
export type TripOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "title" | "description" | "category" | "destination" | "location" | "meetingPoint" | "itinerary" | "whatsIncluded" | "whatsExcluded" | "date" | "endDate" | "maxParticipants" | "price" | "status" | "createdAt" | "updatedAt" | "organizerId", ExtArgs["result"]["trip"]>
export type TripOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "title" | "description" | "category" | "destination" | "location" | "meetingPoint" | "itinerary" | "whatsIncluded" | "whatsExcluded" | "date" | "endDate" | "maxParticipants" | "price" | "vibe" | "status" | "createdAt" | "updatedAt" | "organizerId", ExtArgs["result"]["trip"]>
export type TripInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
organizer?: boolean | Prisma.UserDefaultArgs<ExtArgs>
participants?: boolean | Prisma.Trip$participantsArgs<ExtArgs>
@@ -1481,6 +1526,10 @@ export type $TripPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
endDate: Date | null
maxParticipants: number
price: number
/**
* Ritme/energi trip — dipakai untuk matching dengan vibe user.
*/
vibe: $Enums.Vibe | null
status: $Enums.TripStatus
createdAt: Date
updatedAt: Date
@@ -1926,6 +1975,7 @@ export interface TripFieldRefs {
readonly endDate: Prisma.FieldRef<"Trip", 'DateTime'>
readonly maxParticipants: Prisma.FieldRef<"Trip", 'Int'>
readonly price: Prisma.FieldRef<"Trip", 'Int'>
readonly vibe: Prisma.FieldRef<"Trip", 'Vibe'>
readonly status: Prisma.FieldRef<"Trip", 'TripStatus'>
readonly createdAt: Prisma.FieldRef<"Trip", 'DateTime'>
readonly updatedAt: Prisma.FieldRef<"Trip", 'DateTime'>
+41 -2
View File
@@ -15,7 +15,7 @@ import type * as Prisma from "../internal/prismaNamespace"
/**
* Model UserProfile
* Profil sosial publik. Berisi info yang user pilih untuk dibagikan ke peserta lain
* (bio, kota, minat). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
* (bio, kota, minat, vibe). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
*/
export type UserProfileModel = runtime.Types.Result.DefaultSelection<Prisma.$UserProfilePayload>
@@ -31,6 +31,7 @@ export type UserProfileMinAggregateOutputType = {
bio: string | null
city: string | null
instagram: string | null
vibe: $Enums.Vibe | null
createdAt: Date | null
updatedAt: Date | null
}
@@ -41,6 +42,7 @@ export type UserProfileMaxAggregateOutputType = {
bio: string | null
city: string | null
instagram: string | null
vibe: $Enums.Vibe | null
createdAt: Date | null
updatedAt: Date | null
}
@@ -52,6 +54,7 @@ export type UserProfileCountAggregateOutputType = {
city: number
interests: number
instagram: number
vibe: number
createdAt: number
updatedAt: number
_all: number
@@ -64,6 +67,7 @@ export type UserProfileMinAggregateInputType = {
bio?: true
city?: true
instagram?: true
vibe?: true
createdAt?: true
updatedAt?: true
}
@@ -74,6 +78,7 @@ export type UserProfileMaxAggregateInputType = {
bio?: true
city?: true
instagram?: true
vibe?: true
createdAt?: true
updatedAt?: true
}
@@ -85,6 +90,7 @@ export type UserProfileCountAggregateInputType = {
city?: true
interests?: true
instagram?: true
vibe?: true
createdAt?: true
updatedAt?: true
_all?: true
@@ -169,6 +175,7 @@ export type UserProfileGroupByOutputType = {
city: string | null
interests: string[]
instagram: string | null
vibe: $Enums.Vibe | null
createdAt: Date
updatedAt: Date
_count: UserProfileCountAggregateOutputType | null
@@ -201,6 +208,7 @@ export type UserProfileWhereInput = {
city?: Prisma.StringNullableFilter<"UserProfile"> | string | null
interests?: Prisma.StringNullableListFilter<"UserProfile">
instagram?: Prisma.StringNullableFilter<"UserProfile"> | string | null
vibe?: Prisma.EnumVibeNullableFilter<"UserProfile"> | $Enums.Vibe | null
createdAt?: Prisma.DateTimeFilter<"UserProfile"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"UserProfile"> | Date | string
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
@@ -213,6 +221,7 @@ export type UserProfileOrderByWithRelationInput = {
city?: Prisma.SortOrderInput | Prisma.SortOrder
interests?: Prisma.SortOrder
instagram?: Prisma.SortOrderInput | Prisma.SortOrder
vibe?: Prisma.SortOrderInput | Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
user?: Prisma.UserOrderByWithRelationInput
@@ -228,6 +237,7 @@ export type UserProfileWhereUniqueInput = Prisma.AtLeast<{
city?: Prisma.StringNullableFilter<"UserProfile"> | string | null
interests?: Prisma.StringNullableListFilter<"UserProfile">
instagram?: Prisma.StringNullableFilter<"UserProfile"> | string | null
vibe?: Prisma.EnumVibeNullableFilter<"UserProfile"> | $Enums.Vibe | null
createdAt?: Prisma.DateTimeFilter<"UserProfile"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"UserProfile"> | Date | string
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
@@ -240,6 +250,7 @@ export type UserProfileOrderByWithAggregationInput = {
city?: Prisma.SortOrderInput | Prisma.SortOrder
interests?: Prisma.SortOrder
instagram?: Prisma.SortOrderInput | Prisma.SortOrder
vibe?: Prisma.SortOrderInput | Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
_count?: Prisma.UserProfileCountOrderByAggregateInput
@@ -257,6 +268,7 @@ export type UserProfileScalarWhereWithAggregatesInput = {
city?: Prisma.StringNullableWithAggregatesFilter<"UserProfile"> | string | null
interests?: Prisma.StringNullableListFilter<"UserProfile">
instagram?: Prisma.StringNullableWithAggregatesFilter<"UserProfile"> | string | null
vibe?: Prisma.EnumVibeNullableWithAggregatesFilter<"UserProfile"> | $Enums.Vibe | null
createdAt?: Prisma.DateTimeWithAggregatesFilter<"UserProfile"> | Date | string
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"UserProfile"> | Date | string
}
@@ -267,6 +279,7 @@ export type UserProfileCreateInput = {
city?: string | null
interests?: Prisma.UserProfileCreateinterestsInput | string[]
instagram?: string | null
vibe?: $Enums.Vibe | null
createdAt?: Date | string
updatedAt?: Date | string
user: Prisma.UserCreateNestedOneWithoutProfileInput
@@ -279,6 +292,7 @@ export type UserProfileUncheckedCreateInput = {
city?: string | null
interests?: Prisma.UserProfileCreateinterestsInput | string[]
instagram?: string | null
vibe?: $Enums.Vibe | null
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -289,6 +303,7 @@ export type UserProfileUpdateInput = {
city?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
interests?: Prisma.UserProfileUpdateinterestsInput | string[]
instagram?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
user?: Prisma.UserUpdateOneRequiredWithoutProfileNestedInput
@@ -301,6 +316,7 @@ export type UserProfileUncheckedUpdateInput = {
city?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
interests?: Prisma.UserProfileUpdateinterestsInput | string[]
instagram?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -312,6 +328,7 @@ export type UserProfileCreateManyInput = {
city?: string | null
interests?: Prisma.UserProfileCreateinterestsInput | string[]
instagram?: string | null
vibe?: $Enums.Vibe | null
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -322,6 +339,7 @@ export type UserProfileUpdateManyMutationInput = {
city?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
interests?: Prisma.UserProfileUpdateinterestsInput | string[]
instagram?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -333,6 +351,7 @@ export type UserProfileUncheckedUpdateManyInput = {
city?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
interests?: Prisma.UserProfileUpdateinterestsInput | string[]
instagram?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -357,6 +376,7 @@ export type UserProfileCountOrderByAggregateInput = {
city?: Prisma.SortOrder
interests?: Prisma.SortOrder
instagram?: Prisma.SortOrder
vibe?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
}
@@ -367,6 +387,7 @@ export type UserProfileMaxOrderByAggregateInput = {
bio?: Prisma.SortOrder
city?: Prisma.SortOrder
instagram?: Prisma.SortOrder
vibe?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
}
@@ -377,6 +398,7 @@ export type UserProfileMinOrderByAggregateInput = {
bio?: Prisma.SortOrder
city?: Prisma.SortOrder
instagram?: Prisma.SortOrder
vibe?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
}
@@ -422,12 +444,17 @@ export type UserProfileUpdateinterestsInput = {
push?: string | string[]
}
export type NullableEnumVibeFieldUpdateOperationsInput = {
set?: $Enums.Vibe | null
}
export type UserProfileCreateWithoutUserInput = {
id?: string
bio?: string | null
city?: string | null
interests?: Prisma.UserProfileCreateinterestsInput | string[]
instagram?: string | null
vibe?: $Enums.Vibe | null
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -438,6 +465,7 @@ export type UserProfileUncheckedCreateWithoutUserInput = {
city?: string | null
interests?: Prisma.UserProfileCreateinterestsInput | string[]
instagram?: string | null
vibe?: $Enums.Vibe | null
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -464,6 +492,7 @@ export type UserProfileUpdateWithoutUserInput = {
city?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
interests?: Prisma.UserProfileUpdateinterestsInput | string[]
instagram?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -474,6 +503,7 @@ export type UserProfileUncheckedUpdateWithoutUserInput = {
city?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
interests?: Prisma.UserProfileUpdateinterestsInput | string[]
instagram?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -487,6 +517,7 @@ export type UserProfileSelect<ExtArgs extends runtime.Types.Extensions.InternalA
city?: boolean
interests?: boolean
instagram?: boolean
vibe?: boolean
createdAt?: boolean
updatedAt?: boolean
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
@@ -499,6 +530,7 @@ export type UserProfileSelectCreateManyAndReturn<ExtArgs extends runtime.Types.E
city?: boolean
interests?: boolean
instagram?: boolean
vibe?: boolean
createdAt?: boolean
updatedAt?: boolean
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
@@ -511,6 +543,7 @@ export type UserProfileSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.E
city?: boolean
interests?: boolean
instagram?: boolean
vibe?: boolean
createdAt?: boolean
updatedAt?: boolean
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
@@ -523,11 +556,12 @@ export type UserProfileSelectScalar = {
city?: boolean
interests?: boolean
instagram?: boolean
vibe?: boolean
createdAt?: boolean
updatedAt?: boolean
}
export type UserProfileOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "userId" | "bio" | "city" | "interests" | "instagram" | "createdAt" | "updatedAt", ExtArgs["result"]["userProfile"]>
export type UserProfileOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "userId" | "bio" | "city" | "interests" | "instagram" | "vibe" | "createdAt" | "updatedAt", ExtArgs["result"]["userProfile"]>
export type UserProfileInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
}
@@ -562,6 +596,10 @@ export type $UserProfilePayload<ExtArgs extends runtime.Types.Extensions.Interna
* Username Instagram (tanpa @, opsional)
*/
instagram: string | null
/**
* Gaya jalan / energi user — dipakai untuk matching teman dengan ritme serupa.
*/
vibe: $Enums.Vibe | null
createdAt: Date
updatedAt: Date
}, ExtArgs["result"]["userProfile"]>
@@ -994,6 +1032,7 @@ export interface UserProfileFieldRefs {
readonly city: Prisma.FieldRef<"UserProfile", 'String'>
readonly interests: Prisma.FieldRef<"UserProfile", 'String[]'>
readonly instagram: Prisma.FieldRef<"UserProfile", 'String'>
readonly vibe: Prisma.FieldRef<"UserProfile", 'Vibe'>
readonly createdAt: Prisma.FieldRef<"UserProfile", 'DateTime'>
readonly updatedAt: Prisma.FieldRef<"UserProfile", 'DateTime'>
}
+2
View File
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { SessionProvider } from "@/components/providers/session-provider";
import { Navbar } from "@/components/shared/navbar";
import { ProfileNudgeBanner } from "@/components/shared/profile-nudge-banner";
import { siteConfig, siteUrl } from "@/lib/site";
import "./globals.css";
@@ -80,6 +81,7 @@ export default function RootLayout({
<body className="flex min-h-full flex-col bg-neutral-50">
<SessionProvider>
<Navbar />
<ProfileNudgeBanner />
<main className="flex-1">{children}</main>
</SessionProvider>
</body>
+41 -9
View File
@@ -1,11 +1,25 @@
import type { Metadata } from "next";
import Link from "next/link";
import Image from "next/image";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { tripService } from "@/server/services/trip.service";
import { profileRepo } from "@/server/repositories/profile.repo";
import { TripCard } from "@/features/trip/components/trip-card";
import { siteConfig, siteUrl, absoluteUrl } from "@/lib/site";
import { ACTIVITY_CATEGORIES, categoryMeta } from "@/lib/activity-category";
type OpenTrip = Awaited<ReturnType<typeof tripService.getOpenTrips>>[number];
function mapParticipants(trip: OpenTrip) {
return trip.participants.map((p) => ({
id: p.id,
name: p.user.name,
image: p.user.image,
interests: p.user.profile?.interests ?? [],
}));
}
export const metadata: Metadata = {
title: "Cari Teman Trip & Aktivitas — Pergi Bareng, Bukan Sendiri",
description: `${siteConfig.slogan} ${siteConfig.description}`,
@@ -18,7 +32,14 @@ export const metadata: Metadata = {
};
export default async function HomePage() {
const trips = await tripService.getOpenTrips();
const session = await getServerSession(authOptions);
const [trips, viewerProfile] = await Promise.all([
tripService.getOpenTrips(),
session?.user?.id
? profileRepo.findByUserId(session.user.id)
: Promise.resolve(null),
]);
const viewerInterests = viewerProfile?.interests ?? [];
const now = new Date();
const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
@@ -35,8 +56,10 @@ export default async function HomePage() {
const shownIds = new Set([...upcomingIds, ...latestTrips.map((t) => t.id)]);
const budgetTrips = trips
.filter((t) => !shownIds.has(t.id) && t.price <= 300000)
// Section sosial: trip yang paling ramai joiner-nya (social proof, bukan price proof).
const buzzingTrips = trips
.filter((t) => !shownIds.has(t.id) && t._count.participants > 0)
.sort((a, b) => b._count.participants - a._count.participants)
.slice(0, 3);
const orgJsonLd = {
@@ -191,6 +214,7 @@ export default async function HomePage() {
id={trip.id}
title={trip.title}
category={trip.category}
vibe={trip.vibe}
destination={trip.destination}
location={trip.location}
date={trip.date}
@@ -204,6 +228,8 @@ export default async function HomePage() {
isVerifiedOrganizer={
trip.organizer.organizerVerification?.status === "APPROVED"
}
participants={mapParticipants(trip)}
viewerInterests={viewerInterests}
priority={i === 0}
/>
))}
@@ -261,6 +287,7 @@ export default async function HomePage() {
id={trip.id}
title={trip.title}
category={trip.category}
vibe={trip.vibe}
destination={trip.destination}
location={trip.location}
date={trip.date}
@@ -274,35 +301,38 @@ export default async function HomePage() {
isVerifiedOrganizer={
trip.organizer.organizerVerification?.status === "APPROVED"
}
participants={mapParticipants(trip)}
viewerInterests={viewerInterests}
/>
))}
</div>
)}
</section>
{/* Budget Friendly */}
{budgetTrips.length > 0 && (
{/* Lagi Ramai — social proof, bukan price proof */}
{buzzingTrips.length > 0 && (
<section>
<div className="mb-4 flex items-center gap-3 sm:mb-5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary-100 text-base sm:h-9 sm:w-9 sm:text-lg">
💸
🤝
</div>
<div>
<h2 className="text-base font-bold text-neutral-800 sm:text-lg">
Budget Friendly
Lagi Ramai
</h2>
<p className="text-[11px] text-neutral-500 sm:text-xs">
Trip di bawah Rp 300.000
Banyak yang sudah gabung kamu nggak bakal jalan sendirian
</p>
</div>
</div>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{budgetTrips.map((trip) => (
{buzzingTrips.map((trip) => (
<TripCard
key={trip.id}
id={trip.id}
title={trip.title}
category={trip.category}
vibe={trip.vibe}
destination={trip.destination}
location={trip.location}
date={trip.date}
@@ -316,6 +346,8 @@ export default async function HomePage() {
isVerifiedOrganizer={
trip.organizer.organizerVerification?.status === "APPROVED"
}
participants={mapParticipants(trip)}
viewerInterests={viewerInterests}
/>
))}
</div>
+104
View File
@@ -0,0 +1,104 @@
import type { Metadata } from "next";
import { Suspense } from "react";
import { profileService } from "@/server/services/profile.service";
import { UserCard } from "@/features/profile/components/user-card";
import { PeopleFilter } from "@/features/profile/components/people-filter";
import { isVibe, vibeLabel } from "@/lib/vibe";
import { siteConfig } from "@/lib/site";
interface PeoplePageProps {
searchParams: Promise<{
city?: string;
interest?: string;
vibe?: string;
}>;
}
export async function generateMetadata({
searchParams,
}: PeoplePageProps): Promise<Metadata> {
const { city, interest, vibe: vibeParam } = await searchParams;
const vibe = isVibe(vibeParam) ? vibeParam : undefined;
const parts: string[] = [];
if (vibe) parts.push(`Vibe ${vibeLabel(vibe).toLowerCase()}`);
if (city) parts.push(`di ${city}`);
if (interest) parts.push(`#${interest.toLowerCase()}`);
const title = parts.length
? `Cari Teman ${parts.join(" ")}`
: "Cari Teman Aktivitas — Profil Anggota";
const description = `Telusuri profil anggota ${siteConfig.name} berdasarkan minat, kota, dan vibe. Temukan calon teman trip dengan ritme yang cocok sebelum gabung bareng.`;
return {
title,
description,
alternates: { canonical: "/people" },
openGraph: { title, description, url: "/people" },
};
}
export default async function PeoplePage({ searchParams }: PeoplePageProps) {
const params = await searchParams;
const vibe = isVibe(params.vibe) ? params.vibe : undefined;
const filters = {
city: params.city?.trim() || undefined,
interest: params.interest?.trim().toLowerCase() || undefined,
vibe,
};
const hasFilters = Boolean(filters.city || filters.interest || filters.vibe);
const people = await profileService.findPeople(filters);
return (
<div className="mx-auto max-w-6xl px-4 py-6 sm:py-8">
<div className="mb-5 flex flex-col gap-2 sm:mb-6">
<h1 className="text-xl font-bold text-neutral-800 sm:text-2xl">
Cari Teman Aktivitas
</h1>
<p className="text-sm text-neutral-500">
{hasFilters
? `${people.length} orang ditemukan dengan filter di atas`
: `${people.length} anggota dengan profil sosial — kenali dulu sebelum gabung trip`}
</p>
</div>
<div className="mb-6">
<Suspense fallback={null}>
<PeopleFilter />
</Suspense>
</div>
{people.length === 0 ? (
<div className="rounded-2xl border-2 border-dashed border-neutral-200 bg-white p-8 text-center sm:p-14">
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-primary-50 text-2xl sm:h-16 sm:w-16 sm:text-3xl">
🔍
</div>
<p className="mb-1 text-base font-bold text-neutral-800 sm:text-lg">
{hasFilters
? "Belum ada anggota yang cocok"
: "Belum ada anggota dengan profil terisi"}
</p>
<p className="text-sm text-neutral-500">
{hasFilters
? "Coba longgarkan filter — kota, minat, atau vibe."
: "Setelah anggota lain mengisi profil, mereka akan muncul di sini."}
</p>
</div>
) : (
<ul className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{people.map((u) => (
<li key={u.id}>
<UserCard
id={u.id}
name={u.name}
image={u.image}
isVerifiedOrganizer={
u.organizerVerification?.status === "APPROVED"
}
profile={u.profile}
/>
</li>
))}
</ul>
)}
</div>
);
}
+2
View File
@@ -95,6 +95,7 @@ export default async function ProfilePage() {
city: ownProfile.city,
interests: ownProfile.interests,
instagram: ownProfile.instagram,
vibe: ownProfile.vibe,
}
: null
}
@@ -169,6 +170,7 @@ export default async function ProfilePage() {
id={trip.id}
title={trip.title}
category={trip.category}
vibe={trip.vibe}
destination={trip.destination}
location={trip.location}
date={trip.date}
+79 -19
View File
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getServerSession } from "next-auth";
import Link from "next/link";
import Image from "next/image";
import { authOptions } from "@/lib/auth";
import { tripService } from "@/server/services/trip.service";
import { trustService } from "@/server/services/trust.service";
@@ -15,6 +16,8 @@ import { TripProgramBlock } from "@/features/trip/components/trip-program-block"
import { OrganizerPaymentQueue } from "@/features/booking/components/organizer-payment-queue";
import { ImageGallery } from "@/features/trip/components/image-gallery";
import { TripReviewSection } from "@/features/review/components/trip-review-section";
import { categoryMeta } from "@/lib/activity-category";
import { vibeMeta } from "@/lib/vibe";
import {
isPastTripLastDayForReview,
isTripDepartureDayPast,
@@ -127,6 +130,8 @@ export default async function TripDetailPage({
(p) => p.markedPaidAt && !p.paymentConfirmedAt
);
const catMeta = categoryMeta(trip.category);
const tripUrl = absoluteUrl(`/trips/${trip.id}`);
const eventStatus =
trip.status === "OPEN"
@@ -240,8 +245,21 @@ export default async function TripDetailPage({
<h1 className="text-lg font-bold text-neutral-800 sm:text-xl">
{trip.title}
</h1>
<p className="mt-0.5 flex items-center gap-1.5 text-sm text-neutral-500">
🏔 {trip.destination}
<p className="mt-0.5 flex flex-wrap items-center gap-1.5 text-sm text-neutral-500">
<span aria-hidden>{catMeta.icon}</span>
<span className="rounded-full bg-neutral-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-neutral-600">
{catMeta.label}
</span>
{trip.vibe && (
<span
className="inline-flex items-center gap-0.5 rounded-full bg-secondary-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-secondary-700"
title={vibeMeta(trip.vibe).description}
>
<span aria-hidden>{vibeMeta(trip.vibe).icon}</span>
<span>{vibeMeta(trip.vibe).label}</span>
</span>
)}
<span className="truncate">{trip.destination}</span>
</p>
</div>
<span
@@ -448,9 +466,12 @@ export default async function TripDetailPage({
{/* Peserta yang sudah disetujui organizer (publik) */}
<div>
<h2 className="mb-3 text-xs font-bold text-neutral-700 sm:text-sm">
<h2 className="mb-1 text-xs font-bold text-neutral-700 sm:text-sm">
Peserta terkonfirmasi ({confirmedCount})
</h2>
<p className="mb-3 text-[11px] text-neutral-500 sm:text-xs">
Kenalan dulu sebelum berangkat klik kartu untuk lihat profil.
</p>
{confirmedCount === 0 ? (
<p className="text-xs text-neutral-400 sm:text-sm">
Belum ada peserta yang dikonfirmasi.{" "}
@@ -459,22 +480,61 @@ export default async function TripDetailPage({
: "Jadilah yang pertama mendaftar! 🎒"}
</p>
) : (
<div className="flex flex-wrap gap-1.5 sm:gap-2">
{confirmedParticipants.map((p) => (
<Link
key={p.id}
href={`/u/${p.user.id}`}
className="flex items-center gap-1.5 rounded-full bg-neutral-100 px-2.5 py-1 transition-colors hover:bg-primary-100 sm:gap-2 sm:px-3 sm:py-1.5"
>
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-primary-600 text-[9px] font-bold text-white sm:h-6 sm:w-6 sm:text-[10px]">
{p.user.name.charAt(0).toUpperCase()}
</div>
<span className="text-xs font-medium text-neutral-700 sm:text-sm">
{p.user.name}
</span>
</Link>
))}
</div>
<ul className="grid gap-2 sm:grid-cols-2">
{confirmedParticipants.map((p) => {
const interests = p.user.profile?.interests ?? [];
const city = p.user.profile?.city;
return (
<li key={p.id}>
<Link
href={`/u/${p.user.id}`}
className="flex items-start gap-3 rounded-xl border border-neutral-200 bg-white px-3 py-2.5 transition-colors hover:border-primary-300 hover:bg-primary-50/30"
>
{p.user.image ? (
<Image
src={p.user.image}
alt=""
width={40}
height={40}
className="h-10 w-10 shrink-0 rounded-full object-cover"
/>
) : (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary-600 text-sm font-bold text-white">
{p.user.name.charAt(0).toUpperCase()}
</div>
)}
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-neutral-800">
{p.user.name}
</p>
{city && (
<p className="truncate text-[11px] text-neutral-500 sm:text-xs">
📍 {city}
</p>
)}
{interests.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
{interests.slice(0, 3).map((tag) => (
<span
key={tag}
className="rounded-full bg-secondary-50 px-1.5 py-0.5 text-[10px] font-medium text-secondary-700"
>
#{tag}
</span>
))}
{interests.length > 3 && (
<span className="text-[10px] text-neutral-400">
+{interests.length - 3}
</span>
)}
</div>
)}
</div>
</Link>
</li>
);
})}
</ul>
)}
</div>
</div>
+33 -2
View File
@@ -1,11 +1,21 @@
import type { Metadata } from "next";
import Link from "next/link";
import { Suspense } from "react";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { tripService } from "@/server/services/trip.service";
import { profileRepo } from "@/server/repositories/profile.repo";
import { TripCard } from "@/features/trip/components/trip-card";
import { TripFilter } from "@/features/trip/components/trip-filter";
import { siteConfig } from "@/lib/site";
import { categoryLabel, isActivityCategory } from "@/lib/activity-category";
import { isVibe } from "@/lib/vibe";
import type { GroupSize } from "@/server/repositories/trip.repo";
const GROUP_SIZES: GroupSize[] = ["SMALL", "MEDIUM", "LARGE"];
function isGroupSize(value: unknown): value is GroupSize {
return typeof value === "string" && (GROUP_SIZES as string[]).includes(value);
}
interface TripsPageProps {
searchParams: Promise<{
@@ -13,6 +23,8 @@ interface TripsPageProps {
from?: string;
to?: string;
category?: string;
vibe?: string;
groupSize?: string;
}>;
}
@@ -44,19 +56,30 @@ export async function generateMetadata({
export default async function TripsPage({ searchParams }: TripsPageProps) {
const params = await searchParams;
const category = isActivityCategory(params.category) ? params.category : undefined;
const hasFilters = Boolean(params.q || params.from || params.to || category);
const vibe = isVibe(params.vibe) ? params.vibe : undefined;
const groupSize = isGroupSize(params.groupSize) ? params.groupSize : undefined;
const hasFilters = Boolean(
params.q || params.from || params.to || category || vibe || groupSize
);
const filters = {
q: params.q,
from: params.from,
to: params.to,
category,
vibe,
groupSize,
};
const [trips, allTrips] = await Promise.all([
const session = await getServerSession(authOptions);
const [trips, allTrips, viewerProfile] = await Promise.all([
tripService.getOpenTrips(filters),
hasFilters ? tripService.getOpenTrips() : null,
session?.user?.id
? profileRepo.findByUserId(session.user.id)
: Promise.resolve(null),
]);
const totalCount = hasFilters ? allTrips!.length : trips.length;
const viewerInterests = viewerProfile?.interests ?? [];
return (
<div className="mx-auto max-w-6xl px-4 py-6 sm:py-8">
@@ -120,6 +143,7 @@ export default async function TripsPage({ searchParams }: TripsPageProps) {
id={trip.id}
title={trip.title}
category={trip.category}
vibe={trip.vibe}
destination={trip.destination}
location={trip.location}
date={trip.date}
@@ -133,6 +157,13 @@ export default async function TripsPage({ searchParams }: TripsPageProps) {
isVerifiedOrganizer={
trip.organizer.organizerVerification?.status === "APPROVED"
}
participants={trip.participants.map((p) => ({
id: p.id,
name: p.user.name,
image: p.user.image,
interests: p.user.profile?.interests ?? [],
}))}
viewerInterests={viewerInterests}
/>
))}
</div>
+14
View File
@@ -6,6 +6,7 @@ import { profileService } from "@/server/services/profile.service";
import { TripCard } from "@/features/trip/components/trip-card";
import { ProfileTripRow } from "@/features/profile/components/profile-trip-row";
import { siteConfig } from "@/lib/site";
import { vibeMeta } from "@/lib/vibe";
interface PageProps {
params: Promise<{ id: string }>;
@@ -94,6 +95,18 @@ export default async function PublicProfilePage({ params }: PageProps) {
</p>
)}
{profile?.vibe && (
<div className="mt-3">
<span
className="inline-flex items-center gap-1.5 rounded-full bg-primary-50 px-2.5 py-1 text-xs font-semibold text-primary-700"
title={vibeMeta(profile.vibe).description}
>
<span aria-hidden>{vibeMeta(profile.vibe).icon}</span>
<span>Vibe: {vibeMeta(profile.vibe).label}</span>
</span>
</div>
)}
{profile?.interests && profile.interests.length > 0 && (
<div className="mt-3 flex flex-wrap gap-1.5">
{profile.interests.map((tag) => (
@@ -164,6 +177,7 @@ export default async function PublicProfilePage({ params }: PageProps) {
id={trip.id}
title={trip.title}
category={trip.category}
vibe={trip.vibe}
destination={trip.destination}
location={trip.location}
date={trip.date}