Skip to content

Commit 4d16639

Browse files
mrtysnclaude
andcommitted
Add CV context, URL state sync, and filter utilities
CVContext provides global state (mode, hiddenTags) via React Context and useReducer. URL state encodes to query params (?verbose=true) for shareable links. filterData provides pure functions for tag filtering and short/long content resolution. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c434981 commit 4d16639

File tree

4 files changed

+159
-0
lines changed

4 files changed

+159
-0
lines changed

Mert_Yasin_CV.pdf

0 Bytes
Binary file not shown.

src/context/CVContext.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { createContext, useContext, useReducer, useEffect } from "react";
2+
import { MODES, ACTIONS } from "../constants/tags";
3+
import { decodeStateFromURL, encodeStateToURL } from "../utils/urlState";
4+
5+
const initialState = {
6+
mode: MODES.SHORT,
7+
hiddenTags: [],
8+
preset: null,
9+
};
10+
11+
function cvReducer(state, action) {
12+
switch (action.type) {
13+
case ACTIONS.SET_MODE:
14+
return { ...state, mode: action.payload, preset: null };
15+
16+
case ACTIONS.TOGGLE_TAG: {
17+
const isHidden = state.hiddenTags.includes(action.payload);
18+
return {
19+
...state,
20+
hiddenTags: isHidden
21+
? state.hiddenTags.filter((t) => t !== action.payload)
22+
: [...state.hiddenTags, action.payload],
23+
preset: null,
24+
};
25+
}
26+
27+
case ACTIONS.APPLY_PRESET:
28+
return {
29+
mode: action.payload.mode,
30+
hiddenTags: action.payload.hiddenTags,
31+
preset: action.payload.name,
32+
};
33+
34+
case ACTIONS.RESTORE_FROM_URL:
35+
return { ...action.payload };
36+
37+
default:
38+
return state;
39+
}
40+
}
41+
42+
const CVContext = createContext();
43+
44+
export function CVProvider({ children }) {
45+
const [state, dispatch] = useReducer(cvReducer, initialState, (init) => {
46+
const urlState = decodeStateFromURL();
47+
return urlState || init;
48+
});
49+
50+
useEffect(() => {
51+
encodeStateToURL(state);
52+
}, [state]);
53+
54+
return (
55+
<CVContext.Provider value={{ state, dispatch }}>
56+
{children}
57+
</CVContext.Provider>
58+
);
59+
}
60+
61+
export function useCVContext() {
62+
return useContext(CVContext);
63+
}

src/utils/filterData.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { MODES, TAGS } from "../constants/tags";
2+
3+
/**
4+
* Filter items by tags. An item is hidden only if ALL its tags are in hiddenTags.
5+
* Items with no tags are always shown.
6+
*/
7+
export function filterByTags(items, hiddenTags) {
8+
if (!hiddenTags || hiddenTags.length === 0) return items;
9+
10+
return items.filter((item) => {
11+
if (!item.tags || item.tags.length === 0) return true;
12+
return item.tags.some((tag) => !hiddenTags.includes(tag));
13+
});
14+
}
15+
16+
/**
17+
* Resolve short/long content for an item.
18+
* If mode is SHORT and item has a `short` object, merge short fields over the original.
19+
* A field set to null in `short` means remove that field entirely.
20+
*/
21+
export function resolveItem(item, mode) {
22+
if (mode !== MODES.SHORT || !item.short) return item;
23+
24+
const resolved = { ...item };
25+
for (const [key, value] of Object.entries(item.short)) {
26+
if (value === null) {
27+
delete resolved[key];
28+
} else {
29+
resolved[key] = value;
30+
}
31+
}
32+
delete resolved.short;
33+
return resolved;
34+
}
35+
36+
/**
37+
* Combined: filter by tags, then resolve short/long for remaining items.
38+
*/
39+
export function processData(items, state) {
40+
const filtered = filterByTags(items, state.hiddenTags);
41+
return filtered.map((item) => resolveItem(item, state.mode));
42+
}
43+
44+
/**
45+
* Education-specific processing. Same as processData but also strips
46+
* relevantCourses when the "coursework" tag is hidden.
47+
*/
48+
export function processEducationData(items, state) {
49+
const filtered = filterByTags(items, state.hiddenTags);
50+
const hideCoursework = state.hiddenTags.includes(TAGS.COURSEWORK);
51+
52+
return filtered.map((item) => {
53+
const resolved = resolveItem(item, state.mode);
54+
if (hideCoursework) {
55+
const { relevantCourses, ...rest } = resolved;
56+
return rest;
57+
}
58+
return resolved;
59+
});
60+
}

src/utils/urlState.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { MODES } from "../constants/tags";
2+
3+
export function encodeStateToURL(state) {
4+
const params = new URLSearchParams();
5+
6+
if (state.mode === MODES.LONG) {
7+
params.set("verbose", "true");
8+
}
9+
if (state.hiddenTags.length > 0) {
10+
params.set("hide", state.hiddenTags.join(","));
11+
}
12+
if (state.preset) {
13+
params.set("preset", state.preset);
14+
}
15+
16+
const query = params.toString();
17+
const newURL = query
18+
? `${window.location.pathname}?${query}`
19+
: window.location.pathname;
20+
window.history.replaceState(null, "", newURL);
21+
}
22+
23+
export function decodeStateFromURL() {
24+
const params = new URLSearchParams(window.location.search);
25+
26+
if (!params.has("verbose") && !params.has("hide") && !params.has("preset")) {
27+
return null;
28+
}
29+
30+
const mode = params.get("verbose") === "true" ? MODES.LONG : MODES.SHORT;
31+
const hide = params.get("hide");
32+
const hiddenTags = hide ? hide.split(",").filter(Boolean) : [];
33+
const preset = params.get("preset") || null;
34+
35+
return { mode, hiddenTags, preset };
36+
}

0 commit comments

Comments
 (0)