Skip to content

Commit a89b265

Browse files
committed
role picker profile selections
role picker updates
1 parent 7894b5c commit a89b265

File tree

7 files changed

+288
-17
lines changed

7 files changed

+288
-17
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,5 @@ pnpm-debug.log*
3636

3737
# misc
3838
.DS_Store
39+
40+
.junie

src/auth/authClient.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ export const fetchCurrentUser = async (): Promise<SessionUser> => {
5858
return data;
5959
};
6060

61+
// === Update role (server is the source of truth) ===
62+
export const saveRole = async (
63+
role: "retailer" | "producer" | "enthusiast"
64+
): Promise<SessionUser> => {
65+
const token = storage.getToken();
66+
// Backend expects RoleEnum, which is typically uppercase. Send uppercase to avoid 400.
67+
const body = { role: role.toUpperCase() } as unknown as { role: string };
68+
const {data} = await api.patch(
69+
"/session/user",
70+
body,
71+
{headers: {Authorization: `Bearer ${token}`, "Content-Type": "application/json"}}
72+
);
73+
return data;
74+
};
75+
6176
export const startAuthorization = (userId: string): void => {
6277
if (!userId) {
6378
console.warn("Cannot start Square OAuth: missing userId");

src/auth/authMachine.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export const authMachine = setup({
3535
| { type: "LOGGED_IN"; data: SessionUser }
3636
| { type: "LOGGED_OUT" }
3737
| { type: "POS.LOAD"; provider: PosProvider; merchantId: string }
38-
| { type: "POS.REFRESH"; provider: PosProvider; merchantId: string },
38+
| { type: "POS.REFRESH"; provider: PosProvider; merchantId: string }
39+
| { type: "ROLE.SET"; role: import('./types').Role },
3940
},
4041
actors: {
4142

@@ -70,6 +71,15 @@ export const authMachine = setup({
7071

7172
if (payload) {
7273
const user = payload as SessionUser;
74+
try {
75+
const prevRole = context.user?.user?.role?.value ?? "visitor";
76+
const nextRole = user?.user?.role?.value ?? "visitor";
77+
if (prevRole !== nextRole) {
78+
console.info("[role] machine saveUser role transition", { from: prevRole, to: nextRole, reason: e?.type ?? "unknown" });
79+
}
80+
} catch (err) {
81+
console.warn("[role] saveUser transition logging failed", err);
82+
}
7383
storage.setUser(user);
7484
return user;
7585
}
@@ -93,6 +103,26 @@ export const authMachine = setup({
93103
}),
94104
}),
95105

106+
// Client-only: set role in local user and persist
107+
setRole: assign({
108+
user: ({context, event}) => {
109+
const e = event as { type: string; role?: import('./types').Role };
110+
if (!context.user || !e.role) return context.user;
111+
const next = {
112+
...context.user,
113+
user: {
114+
...context.user.user,
115+
role: {
116+
...context.user.user.role,
117+
value: e.role,
118+
},
119+
},
120+
} as SessionUser;
121+
storage.setUser(next);
122+
return next;
123+
},
124+
}),
125+
96126
setPosLoading: assign({
97127
pos: ({context}) => ({
98128
...context.pos,
@@ -211,7 +241,10 @@ export const authMachine = setup({
211241
},
212242
LOGGED_IN: {
213243
actions: "saveUser"
214-
}
244+
},
245+
"ROLE.SET": {
246+
actions: "setRole",
247+
},
215248
},
216249
// Nested state for authenticated
217250
states: {

src/auth/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ type UserRole = {
1616
export type SessionUser = {
1717
user: User;
1818
token: string;
19-
permissions: string[];
2019
};
2120

2221
export type PosToken = {
@@ -40,6 +39,7 @@ export type AuthContextValue = {
4039
login: (data: SessionUser) => void;
4140
logout: () => void;
4241
fetchUser: () => Promise<SessionUser | null>;
42+
updateRole: (role: Role) => void;
4343
role: Role;
4444
isVisitor: boolean;
4545
isRetailer: boolean;

src/auth/useAuthService.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {useSelector} from "@xstate/react";
22
import type {ActorRefFrom} from "xstate";
33
import {authMachine} from "./authMachine";
44
import type {Role, SessionUser} from "./types";
5-
import {fetchCurrentUser} from "./authClient.ts";
5+
import {fetchCurrentUser, saveRole} from "./authClient.ts";
66

77
type AuthService = ActorRefFrom<typeof authMachine>;
88

@@ -51,6 +51,39 @@ export const useAuthService = (service: AuthService) => {
5151
refreshPos: async (provider: "square" | "clover", merchantId: string): Promise<void> => {
5252
send({type: "POS.REFRESH", provider, merchantId});
5353
},
54+
updateRole: (nextRole: Role) => {
55+
// Disallow setting non-selectable roles from UI
56+
if (nextRole === "visitor" || nextRole === "admin") {
57+
console.warn("[role] blocked attempt to set disallowed role", { nextRole });
58+
return;
59+
}
60+
// Persist to backend and rehydrate session user
61+
(async () => {
62+
try {
63+
console.info("[role] updating role →", { from: role, to: nextRole });
64+
const updated = await saveRole(nextRole as any);
65+
const newRole = (updated?.user?.role?.value ?? "visitor") as Role;
66+
console.info("[role] role updated ✔", { from: role, to: newRole, userId: updated?.user?.id });
67+
send({type: "LOGGED_IN", data: updated});
68+
69+
// After a successful role change, redirect to role-specific profile when applicable
70+
const normalized = String(newRole).toLowerCase() as Role;
71+
if (normalized === "retailer") {
72+
const retailerId = updated?.user?.role?.id;
73+
if (retailerId) {
74+
const targetPath = `/retailer/${retailerId}/profile`;
75+
if (window.location.pathname !== targetPath) {
76+
window.location.assign(targetPath);
77+
}
78+
} else {
79+
console.warn("[role] retailer selected but missing role.id; staying on current page");
80+
}
81+
}
82+
} catch (e) {
83+
console.error("[role] update failed ✖", e);
84+
}
85+
})();
86+
},
5487
role,
5588
isVisitor: role === "visitor",
5689
isRetailer: role === "retailer",

src/pages/Home.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ import Hero from "../components/common/Hero.tsx";
66
* @param type
77
*/
88
export const HomePage: React.FC<{ userType: string }> = (type) => {
9-
if (type.userType === "visitor") {
10-
return <VisitorHomePage />;
11-
} else if (type.userType === "retailer") {
12-
return <RetailerHomePage />;
9+
switch ((type.userType || "visitor").toLowerCase()) {
10+
case "retailer":
11+
return <RetailerHomePage />;
12+
case "producer":
13+
return <ProducerHomePage />;
14+
case "enthusiast":
15+
return <EnthusiastHomePage />;
16+
case "visitor":
17+
default:
18+
return <VisitorHomePage />;
1319
}
1420
};
1521

@@ -30,3 +36,21 @@ const RetailerHomePage = () => {
3036
/>
3137
);
3238
};
39+
40+
const ProducerHomePage = () => {
41+
return (
42+
<Hero
43+
subHeading="Showcase Your Craft"
44+
desc="Set up your producer profile, present your portfolio, and connect with retailers and enthusiasts discovering your wines."
45+
/>
46+
);
47+
};
48+
49+
const EnthusiastHomePage = () => {
50+
return (
51+
<Hero
52+
subHeading="Explore, Learn, Enjoy"
53+
desc="Browse wineries and retailers, save favorites, and deepen your wine knowledge — read-only for now."
54+
/>
55+
);
56+
};

0 commit comments

Comments
 (0)