Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
371f31c
Folder structure changes for shared files in Sign up and User Profile
Allimonae Jun 26, 2026
05d4e64
Folder structure changes for User Profile / Sign up form shared files
Allimonae Jun 26, 2026
548c654
Added content to UserProfile.page
Allimonae Jun 26, 2026
dfcda10
Folder structure for members backend
Allimonae Jun 29, 2026
5abe3c3
Member.java done
Allimonae Jun 29, 2026
6dcf71b
Updated field named in members db, MemberRepo.java done
Allimonae Jun 29, 2026
3f518c9
Active updated to required, MemberDto.java done
Allimonae Jun 29, 2026
1dddd3a
MemberService.java, modified return types in MemberRepo.java
Allimonae Jun 29, 2026
164cbcd
added dto records CreateMemberRequest and UpdateMemberRequest. Update…
Allimonae Jul 6, 2026
aa8023f
Naming convention editMember -> updateMember in MemberRepo. Created M…
Allimonae Jul 6, 2026
b3a9786
Added MemberController
Allimonae Jul 6, 2026
db591a2
Removed member fields not accessible by frontend from Mem
Allimonae Jul 6, 2026
8763c21
MemberController - Added id as param in updateMember, MemberService -…
Allimonae Jul 6, 2026
68e821f
Removed member files from common and moved to api/member
Allimonae Jul 10, 2026
1444c48
Updated imports due to folder structure change
Allimonae Jul 10, 2026
677a83c
Separated exceptions from MemberService.java, created new classes for…
Allimonae Jul 10, 2026
f5d3ae4
createMember implemented in MemberSqlRepo. createdAt and updatedAt ar…
Allimonae Jul 13, 2026
9ac716f
MemberSqlRepo createMember
Allimonae Jul 13, 2026
e100bb0
Restored V0001 db create members, added V0004 alter members
Allimonae Jul 13, 2026
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
7 changes: 7 additions & 0 deletions db/migration/V0004__Alter_members_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ALTER TABLE members RENAME COLUMN linked_url TO linked_in_url;
ALTER TABLE members RENAME COLUMN bio TO introduction;
ALTER TABLE members RENAME COLUMN industry TO industry_pref;
ALTER TABLE members RENAME COLUMN role TO role_pref;

ALTER TABLE members ALTER COLUMN introduction SET NOT NULL;
ALTER TABLE members ALTER COLUMN active SET NOT NULL DEFAULT TRUE;
34 changes: 17 additions & 17 deletions js/src/features/sign-up/components/SignUpForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { signUpFormSchema } from "@/features/sign-up/api/schemas";
import {
INDUSTRIES,
MATCHING_PREFERENCES,
} from "@/features/sign-up/components/signUpFormConfig";
import { SignUpFormValues } from "@/features/sign-up/types";
import { userProfileSchema } from "@/features/user-profile/api/schemas";
import { UserProfileValues } from "@/features/user-profile/types";
import {
Alert,
Button,
Expand All @@ -27,7 +27,7 @@ const matchingPreferenceOptions = MATCHING_PREFERENCES.map((v) => ({
const industryOptions = INDUSTRIES.map((v) => ({ value: v, label: v }));

// Define initial values
const initialFormValues: SignUpFormValues = {
const initialFormValues: UserProfileValues = {
fullName: "",
email: "",
linkedin: "",
Expand All @@ -42,12 +42,12 @@ const initialFormValues: SignUpFormValues = {

export function SignUpForm() {
// State management
const [values, setValues] = useState<SignUpFormValues>(() => ({
const [values, setValues] = useState<UserProfileValues>(() => ({
...initialFormValues,
}));

const [errors, setErrors] = useState<
Partial<Record<keyof SignUpFormValues, string>>
Partial<Record<keyof UserProfileValues, string>>
>({});

const [isSubmitting, setIsSubmitting] = useState(false);
Expand All @@ -65,9 +65,9 @@ export function SignUpForm() {
};

// Handlers
const handleFieldChange = <K extends keyof SignUpFormValues>(
const handleFieldChange = <K extends keyof UserProfileValues>(
field: K,
value: SignUpFormValues[K],
value: UserProfileValues[K],
) => {
setValues((current) => ({ ...current, [field]: value }));
setErrors((current) => ({ ...current, [field]: undefined }));
Expand All @@ -76,11 +76,11 @@ export function SignUpForm() {
};

const handleFieldBlur = (
field: keyof SignUpFormValues,
field: keyof UserProfileValues,
overrideValue?: string,
) => {
const valueToValidate = overrideValue ?? values[field];
const fieldSchema = signUpFormSchema.pick({ [field]: true } as Record<
const fieldSchema = userProfileSchema.pick({ [field]: true } as Record<
typeof field,
true
>);
Expand All @@ -92,15 +92,15 @@ export function SignUpForm() {
};

// Validation logic
const validateForm = (values: SignUpFormValues) => {
const result = signUpFormSchema.safeParse(values);
const validateForm = (values: UserProfileValues) => {
const result = userProfileSchema.safeParse(values);
const fieldErrors =
result.success ? {} : result.error?.flatten().fieldErrors;
const errors = {} as Partial<Record<keyof SignUpFormValues, string>>;
const errors = {} as Partial<Record<keyof UserProfileValues, string>>;
for (const key in fieldErrors) {
if (fieldErrors[key as keyof SignUpFormValues]?.[0]) {
errors[key as keyof SignUpFormValues] =
fieldErrors[key as keyof SignUpFormValues]?.[0];
if (fieldErrors[key as keyof UserProfileValues]?.[0]) {
errors[key as keyof UserProfileValues] =
fieldErrors[key as keyof UserProfileValues]?.[0];
}
}
return errors;
Expand Down Expand Up @@ -211,7 +211,7 @@ export function SignUpForm() {
onChange={(value) =>
handleFieldChange(
"matchingPreference",
(value || "") as SignUpFormValues["matchingPreference"],
(value || "") as UserProfileValues["matchingPreference"],
)
}
onBlur={() => handleFieldBlur("matchingPreference")}
Expand All @@ -225,7 +225,7 @@ export function SignUpForm() {
onChange={(value) =>
handleFieldChange(
"industry",
(value || "") as SignUpFormValues["industry"],
(value || "") as UserProfileValues["industry"],
)
}
onBlur={() => handleFieldBlur("industry")}
Expand Down
8 changes: 0 additions & 8 deletions js/src/features/sign-up/components/signUpFormConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,6 @@ export const MATCHING_PREFERENCES = [
"No Preference - I am open to any type of match",
] as const;

export const TALKING_POINTS = [
Comment thread
spiffyy99 marked this conversation as resolved.
"Career journey",
"Advice",
"Current events",
"Hobbies",
"Other",
] as const;

export const INDUSTRIES = [
"Technology",
"Finance",
Expand Down
9 changes: 9 additions & 0 deletions js/src/features/user-profile/UserProfile.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Stack, Title } from "@mantine/core";

export function UserProfilePage() {
return (
<Stack gap="xl">
<Title order={2}>User Profile</Title>
</Stack>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";

export const signUpFormSchema = z.object({
export const userProfileSchema = z.object({
fullName: z.string().min(1, "Full Name is required."),
email: z
.string()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface SignUpFormValues {
export interface UserProfileValues {
fullName: string;
email: string;
linkedin: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.patinanetwork.patchats.api.member;

import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.patinanetwork.patchats.api.member.dto.CreateMemberRequest;
import org.patinanetwork.patchats.api.member.dto.MemberDto;
import org.patinanetwork.patchats.common.dto.ApiResponder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/members")
@Tag(name = "Member")
@RequiredArgsConstructor
public class MemberController {

private final MemberService memberService;

@PostMapping
public ResponseEntity<ApiResponder<MemberDto>> createMember(@Valid @RequestBody final CreateMemberRequest request) {
final MemberDto response = memberService.createMember(request);
return ResponseEntity.ok(ApiResponder.success("Member created successfully", response));
}

// TODO: Implement these endpoints after createMember is fully functional and tested

Check warning on line 29 in src/main/java/org/patinanetwork/patchats/api/member/MemberController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=Patina-Network_patchats&issues=AZ9cTMNOqB1O_tn33SOG&open=AZ9cTMNOqB1O_tn33SOG&pullRequest=43
// @PatchMapping("/{id}")
// public ResponseEntity<ApiResponder<MemberDto>> updateMember(
// @Valid @RequestBody final UpdateMemberRequest request, @PathVariable final UUID id) {

Check warning on line 32 in src/main/java/org/patinanetwork/patchats/api/member/MemberController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=Patina-Network_patchats&issues=AZ9cTMNOqB1O_tn33SOD&open=AZ9cTMNOqB1O_tn33SOD&pullRequest=43
// final MemberDto response = memberService.updateMember(request, id);
// return ResponseEntity.ok(ApiResponder.success("Member updated successfully", response));
// }

// @GetMapping("/{id}")
// public ResponseEntity<ApiResponder<MemberDto>> getMember(@PathVariable final UUID id) {

Check warning on line 38 in src/main/java/org/patinanetwork/patchats/api/member/MemberController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=Patina-Network_patchats&issues=AZ9cTMNOqB1O_tn33SOE&open=AZ9cTMNOqB1O_tn33SOE&pullRequest=43
// final MemberDto response = memberService.getMemberById(id);
// return ResponseEntity.ok(ApiResponder.success("Member retrieved successfully", response));
// }

// @DeleteMapping("/{id}")
// public ResponseEntity<ApiResponder<MemberDto>> deactivateMember(@PathVariable final UUID id) {

Check warning on line 44 in src/main/java/org/patinanetwork/patchats/api/member/MemberController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=Patina-Network_patchats&issues=AZ9cTMNOqB1O_tn33SOF&open=AZ9cTMNOqB1O_tn33SOF&pullRequest=43
// final MemberDto response = memberService.deactivateMemberById(id);
// return ResponseEntity.ok(ApiResponder.success("Member deactivated successfully", response));
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.patinanetwork.patchats.api.member;

import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.patinanetwork.patchats.api.member.db.models.Member;
import org.patinanetwork.patchats.api.member.db.repos.MemberRepo;
import org.patinanetwork.patchats.api.member.dto.CreateMemberRequest;
import org.patinanetwork.patchats.api.member.dto.MemberDto;
import org.patinanetwork.patchats.api.member.dto.UpdateMemberRequest;
import org.patinanetwork.patchats.common.web.exception.MemberDuplicateException;
import org.patinanetwork.patchats.common.web.exception.MemberNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

@Service
@RequiredArgsConstructor
public class MemberService {

private final MemberRepo memberRepo;

public MemberDto createMember(CreateMemberRequest request) {
if (memberRepo.getMemberByEmail(request.email()).isPresent()) {
throw new MemberDuplicateException(request.email());
}
Member member = Member.builder()

@spiffyy99 spiffyy99 Jul 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to just use the existing member object from the repo? We already check if it exists above.

If we're creating a whole new member object, we might be losing information from the old object. (We can't assume that the update request has all the same data as the member object, there may be some stuff we don't want to update :))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to highlight the member builder in updateMember? If so, i'll work on making the updateMember support partial updates rather than full updates, and not have to build a member for every update.

.id(UUID.randomUUID())
.fullName(request.fullName())
.email(request.email())
.linkedInUrl(request.linkedInUrl())
.introduction(request.introduction())
.referralSource(request.referralSource())
.active(true)
.matchPref(request.matchPref())
.industryPref(request.industryPref())
.rolePref(request.rolePref())
.topics(request.topics())
.extraNotes(request.extraNotes())
.build();
Member createdMember = memberRepo.createMember(member);
return MemberDto.from(createdMember);
}

public MemberDto updateMember(UpdateMemberRequest request, UUID id) {
if (memberRepo.getMemberById(id).isEmpty()) {
throw new MemberNotFoundException(id);
}
// TODO: Implement partial update instead of full update

Check warning on line 48 in src/main/java/org/patinanetwork/patchats/api/member/MemberService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=Patina-Network_patchats&issues=AZ9cTMJWqB1O_tn33SN_&open=AZ9cTMJWqB1O_tn33SN_&pullRequest=43
Member member = Member.builder()
.id(id)
.fullName(request.fullName())
.email(request.email())
.linkedInUrl(request.linkedInUrl())
.introduction(request.introduction())
.matchPref(request.matchPref())
.industryPref(request.industryPref())
.rolePref(request.rolePref())
.topics(request.topics())
.extraNotes(request.extraNotes())
.build();
Member updatedMember = memberRepo
.updateMember(member)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Member not found"));
return MemberDto.from(updatedMember);
}

// TODO: Implement these methods after createMember and updateMember is fully functional and tested

Check warning on line 67 in src/main/java/org/patinanetwork/patchats/api/member/MemberService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=Patina-Network_patchats&issues=AZ9cTMJWqB1O_tn33SOC&open=AZ9cTMJWqB1O_tn33SOC&pullRequest=43
// public MemberDto getMemberById(UUID id) {

Check warning on line 68 in src/main/java/org/patinanetwork/patchats/api/member/MemberService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=Patina-Network_patchats&issues=AZ9cTMJWqB1O_tn33SOA&open=AZ9cTMJWqB1O_tn33SOA&pullRequest=43
// return memberRepo
// .getMemberById(id)
// .map(MemberDto::from)
// .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Member not found"));
// }

// public MemberDto deactivateMemberById(UUID id) {

Check warning on line 75 in src/main/java/org/patinanetwork/patchats/api/member/MemberService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=Patina-Network_patchats&issues=AZ9cTMJWqB1O_tn33SOB&open=AZ9cTMJWqB1O_tn33SOB&pullRequest=43
// return memberRepo
// .deactivateMemberById(id)
// .map(MemberDto::from)
// .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Member not found"));
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.patinanetwork.patchats.api.member.db.models;

import java.time.Instant;
import java.util.UUID;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Builder
@ToString
@EqualsAndHashCode
public class Member {

private UUID id;

@Setter
private String fullName;

@Setter
private String email;

@Setter
private String linkedInUrl;

@Setter
private String introduction;

@Setter
private String referralSource;

@Setter
private boolean active;

@Setter
private String matchPref;

@Setter
private String industryPref;

@Setter
private String rolePref;

@Setter
private String topics;

@Setter
private String extraNotes;

private Instant createdAt;

private Instant updatedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.patinanetwork.patchats.api.member.db.repos;

import java.util.Optional;
import java.util.UUID;
import org.patinanetwork.patchats.api.member.db.models.Member;

public interface MemberRepo {
/**
* @note - The provided object's methods will be overridden with any returned data from the database.
* @param member - required fields:
* <ul>
* <li>id
* <li>fullName
* <li>email
* <li>introduction
* <li>active
* <li>createdAt
* <li>updatedAt
* </ul>
*/
Member createMember(Member member);

/**
* @note - The provided object's methods will be overridden with any returned data from the database.
* @param member - overridden fields:
* <ul>
* <li>fullName
* <li>email
* <li>linkedInUrl
* <li>introduction
* <li>referralSource
* <li>active
* <li>matchPref
* <li>industryPref
* <li>rolePref
* <li>topics
* <li>extraNotes
* </ul>
*/
Optional<Member> updateMember(Member member);

Optional<Member> getMemberById(UUID id);

Optional<Member> getMemberByEmail(String email);

Optional<Member> deactivateMemberById(UUID id);
}
Loading
Loading