codingpixel-expo-app 0.1.0 → 0.1.2

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/dist/fonts.js CHANGED
@@ -81,7 +81,9 @@ export function generateBottomSheetProviderBlocks(bottomSheet) {
81
81
  }
82
82
  /**
83
83
  * Convenience: build the full sentinel-replacement map for `patchLayout`.
84
- * Includes both layout sentinels and the fonts.ts sentinel.
84
+ * Note: `fonts.ts` ships as a static enum file (Deviation #10) — no
85
+ * `FONTS_OBJECT` sentinel is patched. The 6 `_layout.tsx` sentinels still get
86
+ * filled (or empty-dropped per `gatherAnswers`).
85
87
  */
86
88
  export function buildLayoutReplacements(answers) {
87
89
  const fonts = generateUseFontsBlocks(answers.primaryFont, answers.secondaryFont);
@@ -93,6 +95,5 @@ export function buildLayoutReplacements(answers) {
93
95
  BOTTOM_SHEET_PROVIDER_IMPORT: bs.importBlock,
94
96
  BOTTOM_SHEET_PROVIDER_OPEN: bs.openBlock,
95
97
  BOTTOM_SHEET_PROVIDER_CLOSE: bs.closeBlock,
96
- FONTS_OBJECT: generateFontsObject(answers.primaryFont, answers.secondaryFont),
97
98
  };
98
99
  }
package/dist/install.js CHANGED
@@ -38,6 +38,11 @@ export function buildAlwaysInstalledList(workletsPkg) {
38
38
  // Deviation #7 — Phase 7 patchBabel injects ["module-resolver", { alias }];
39
39
  // Metro fails on first bundle if the plugin pkg isn't installed.
40
40
  "babel-plugin-module-resolver",
41
+ // Deviation #11 — expo-router peer deps that `expo install expo-router`
42
+ // does NOT auto-install (Metro fails at bundle time with "Unable to
43
+ // resolve 'expo-linking' from .../expo-router/build/views/Unmatched.js").
44
+ "expo-linking",
45
+ "expo-constants",
41
46
  ];
42
47
  }
43
48
  export function buildConditionalDeps(answers) {
package/dist/patch.js CHANGED
@@ -19,6 +19,28 @@ export function slugify(name) {
19
19
  .replace(/^-+|-+$/g, "")
20
20
  .toLowerCase() || "app");
21
21
  }
22
+ /**
23
+ * Build a reverse-DNS bundle identifier segment from the app name.
24
+ *
25
+ * Android `package` + iOS `bundleIdentifier` constraints:
26
+ * - Lowercase letters / digits only per segment.
27
+ * - No dashes, dots, or other punctuation INSIDE a segment.
28
+ * - Each segment must START with a letter (no leading digits).
29
+ * - At least two segments separated by dots.
30
+ *
31
+ * `my-test-app` → `mytestapp` → final ID `com.codingpixel.mytestapp`.
32
+ * `1pp` → `app1pp` (leading-digit guard).
33
+ */
34
+ export function bundleIdSegment(name) {
35
+ let seg = slugify(name).replace(/-/g, "");
36
+ if (!/^[a-z]/.test(seg))
37
+ seg = `app${seg}`;
38
+ return seg || "app";
39
+ }
40
+ /** Compose the full bundle identifier with the `com.codingpixel.` namespace prefix. */
41
+ export function bundleIdFor(name) {
42
+ return `com.codingpixel.${bundleIdSegment(name)}`;
43
+ }
22
44
  function readJson(p) {
23
45
  return JSON.parse(fs.readFileSync(p, "utf8"));
24
46
  }
@@ -46,6 +68,16 @@ export function patchAppJson(target, name, _answers) {
46
68
  json.expo.name = name;
47
69
  json.expo.slug = slugify(name);
48
70
  json.expo.scheme = slugify(name);
71
+ // android.package + ios.bundleIdentifier — required to launch the app on
72
+ // either platform (Expo CLI errors with "Required property ... is not found"
73
+ // otherwise). Preserve any user-set value; only fill when missing.
74
+ const bundleId = bundleIdFor(name);
75
+ json.expo.ios ??= {};
76
+ if (!json.expo.ios.bundleIdentifier)
77
+ json.expo.ios.bundleIdentifier = bundleId;
78
+ json.expo.android ??= {};
79
+ if (!json.expo.android.package)
80
+ json.expo.android.package = bundleId;
49
81
  json.expo.plugins ??= [];
50
82
  if (!json.expo.plugins.some((e) => nameOf(e) === "expo-router")) {
51
83
  json.expo.plugins.push("expo-router");
@@ -112,18 +144,17 @@ export function patchConstants(target, templatesRoot, answers) {
112
144
  const out = applySentinels(source, { MEDIA_CONSTANTS: replacement }, p);
113
145
  fs.writeFileSync(p, out);
114
146
  }
115
- // ---------- layout + fonts splice (Phase 6 step 4) ----------
147
+ // ---------- layout splice (Phase 6 step 4) ----------
148
+ // Deviation #10: `fonts.ts` ships as a static enum file (no FONTS_OBJECT
149
+ // sentinel), so this only touches `_layout.tsx`.
116
150
  export function patchLayout(target, replacements) {
117
151
  const layoutPath = path.join(target, "src/app/_layout.tsx");
118
- const fontsPath = path.join(target, "src/ui/theme/fonts.ts");
119
- for (const p of [layoutPath, fontsPath]) {
120
- if (!fileExists(p)) {
121
- throw new Error(`patchLayout: ${p} missing — was applyBase run first?`);
122
- }
123
- const before = fs.readFileSync(p, "utf8");
124
- const after = applySentinels(before, replacements, p);
125
- fs.writeFileSync(p, after);
152
+ if (!fileExists(layoutPath)) {
153
+ throw new Error(`patchLayout: ${layoutPath} missing was applyBase run first?`);
126
154
  }
155
+ const before = fs.readFileSync(layoutPath, "utf8");
156
+ const after = applySentinels(before, replacements, layoutPath);
157
+ fs.writeFileSync(layoutPath, after);
127
158
  }
128
159
  // ---------- package.json scripts (Phase 7 step 1) ----------
129
160
  export function patchPackageJsonScripts(target) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codingpixel-expo-app",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Opinionated Expo app scaffolder mirroring MyRoster conventions.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@ import { combineReducers } from "@reduxjs/toolkit";
2
2
  import { persistReducer } from "redux-persist";
3
3
 
4
4
  import { reduxStorage } from "./mmkvStorage";
5
- import { userReducer } from "./slices/userSlice";
5
+ import userReducer from "./slices/userSlice";
6
6
 
7
7
  export const rootReducer = combineReducers({
8
8
  user: userReducer,
@@ -1,13 +1,16 @@
1
- // Deviation #5 (docs/MIRROR_NOTES.md): minimal user shape per SPEC §6 ("dummy
2
- // user shape"). Apps replace with their actual auth model.
1
+ // Minimal user shape per SPEC §6 ("dummy user shape").
2
+ // `accessToken` is required by the axios interceptor in `core/utils/config.ts`
3
+ // — every request reads `store.getState().user?.accessToken` and attaches as
4
+ // `Bearer <token>` if present. Apps replace shape but must keep the field.
3
5
  import { createSlice, PayloadAction } from "@reduxjs/toolkit";
4
6
 
5
7
  export type User = {
6
8
  id: string | null;
7
9
  name: string | null;
10
+ accessToken: string | null;
8
11
  };
9
12
 
10
- const initialState: User = { id: null, name: null };
13
+ const initialState: User = { id: null, name: null, accessToken: null };
11
14
 
12
15
  const userSlice = createSlice({
13
16
  name: "user",
@@ -16,16 +19,21 @@ const userSlice = createSlice({
16
19
  setUser: (state, action: PayloadAction<User>) => {
17
20
  state.id = action.payload.id;
18
21
  state.name = action.payload.name;
22
+ state.accessToken = action.payload.accessToken;
19
23
  },
20
24
  updateUser: (state, action: PayloadAction<Partial<User>>) => {
21
25
  Object.assign(state, action.payload);
22
26
  },
27
+ setAccessToken: (state, action: PayloadAction<string | null>) => {
28
+ state.accessToken = action.payload;
29
+ },
23
30
  clearUser: (state) => {
24
31
  state.id = null;
25
32
  state.name = null;
33
+ state.accessToken = null;
26
34
  },
27
35
  },
28
36
  });
29
37
 
30
- export const { setUser, updateUser, clearUser } = userSlice.actions;
38
+ export const { setUser, updateUser, setAccessToken, clearUser } = userSlice.actions;
31
39
  export default userSlice.reducer;
@@ -1,42 +1,3 @@
1
- export const TANSTACK_KEYS = {
2
- // coach
3
-
4
- // Dashboard
5
- dashboard: ["coach", "dashboard"],
6
- // Teams
7
- teams: ["coach", "teams"],
8
- teamDetails: (teamId: string) => ["coach", "teamDetails", teamId],
9
- // Schedule
10
- schedule: ["coach", "schedule"],
11
- // Messages
12
- messages: ["coach", "messages"],
13
- messageDetails: (messageId: string) => ["coach", "messageDetails", messageId],
14
- directChatInfo: (chatId: string) => ["directChatInfo", chatId],
15
- directChatMessages: (chatId: string) => ["directChatMessages", chatId],
16
- teamChatInfo: (teamId: string) => ["teamChatInfo", teamId],
17
- teamChatMessages: (teamId: string) => ["teamChatMessages", teamId],
18
- teamAnnouncements: (teamId: string) => ["teamAnnouncements", teamId],
19
- // Payments
20
- payments: ["coach", "payments"],
21
- paymentsSummary: ["coach", "payments", "summary"],
22
- paymentPlanDetail: (planId: string) => ["coach", "paymentPlanDetail", planId],
23
- // Profile
24
-
25
- // shared
26
- playerDetails: (playerId: string) => ["shared", "playerDetails", playerId],
27
-
28
- // parent
29
- parentSchedule: ["parent", "schedule"],
30
- parentDashboard: ["parent", "dashboard"],
31
- myFamily: ["parent", "myFamily"],
32
- programs: ["parent", "programs"],
33
- programDetails: (id: string) => ["parent", "programDetails", id],
34
- registration: ["parent", "registration"],
35
- parentMessages: ["parent", "messages"],
36
- parentMessageDetails: (messageId: string) => ["parent", "messageDetails", messageId],
37
- parentPayments: ["parent", "payments"],
38
- parentPaymentsSummary: ["parent", "payments", "summary"],
39
- parentOrganizations: ["parent", "organizations"],
40
- parentTeamInvitations: ["parent", "teamInvitations"],
41
- parentTeamInvitationDetail: (id: string) => ["parent", "teamInvitationDetail", id],
42
- };
1
+ // Empty registry apps populate with their own query keys.
2
+ // Pattern: { listKey: ["list"], detailKey: (id: string) => ["detail", id] }
3
+ export const TANSTACK_KEYS = {};
@@ -1,29 +1,5 @@
1
- const SOCKET_URL = "";
2
-
3
- const BASE_URL = `${SOCKET_URL}api/`;
4
-
5
- // placeholder update when backend API is finalized
6
- const ENDPOINTS = {
7
- LOGIN: "auth/login",
8
- // Dashboard
9
- DASHBOARD: "coach/dashboard",
10
- // Teams
11
- TEAMS: "coach/teams",
12
- TEAM_DETAILS: (teamId: string) => `coach/teams/${teamId}`,
13
- // Schedule
14
- SCHEDULE: "coach/schedule",
15
- // Messages
16
- MESSAGES: "coach/messages",
17
- MESSAGE_DETAILS: (messageId: string) => `coach/messages/${messageId}`,
18
- DIRECT_CHAT: (chatId: string) => `chat/direct/${chatId}`,
19
- TEAM_CHAT: (teamId: string) => `chat/team/${teamId}`,
20
- // Payments
21
- PAYMENTS: "coach/payments",
22
- PAYMENTS_SUMMARY: "coach/payments/summary",
23
- PAYMENT_PLAN: (planId: string) => `coach/payment-plans/${planId}`,
24
- // Profile
25
- PROFILE: "coach/profile",
26
- CHANGE_PASSWORD: "coach/change-password",
27
- };
28
-
29
- export { BASE_URL, ENDPOINTS, SOCKET_URL };
1
+ // Empty endpoint registry — apps populate as backend API solidifies.
2
+ // `BASE_URL` is consumed by `core/utils/config.ts` (axios baseURL).
3
+ export const SOCKET_URL = "";
4
+ export const BASE_URL = "";
5
+ export const ENDPOINTS = {};
@@ -1,47 +1,9 @@
1
+ // Empty type registry — apps add domain types as features grow.
2
+ // `InsetsProps` is consumed by `ui/appComponents/appButton` and a few other
3
+ // shipped primitives, so it stays.
1
4
  export interface InsetsProps {
2
5
  top?: number;
3
6
  bottom?: number;
4
7
  left?: number;
5
8
  right?: number;
6
9
  }
7
-
8
- export type TeamNextEvent = {
9
- id: string;
10
- monthLabel: string;
11
- dayLabel: string;
12
- title: string;
13
- timeAndLocationLabel: string;
14
- kind: ScheduleEventKind;
15
- };
16
-
17
- export type TeamStatus = "active" | "inactive";
18
- export type ScheduleEventKind = "game" | "practice";
19
-
20
- export type Team = {
21
- id: string;
22
- name: string;
23
- seasonLabel: string;
24
- status: TeamStatus;
25
- logoUri?: string;
26
- playerCount: number;
27
- coachCount: number;
28
- eventCount: number;
29
- nextEvent: TeamNextEvent | null;
30
- };
31
-
32
- export type ScheduleRow = {
33
- id: string;
34
- monthLabel: string;
35
- dayLabel: string;
36
- title: string;
37
- timeAndLocationLabel: string;
38
- kind: ScheduleEventKind;
39
- };
40
-
41
- export type PlayerTransaction = {
42
- id: string;
43
- invoiceNumber: string;
44
- date: string;
45
- amount: string;
46
- status: "paid" | "unpaid";
47
- };
@@ -1,3 +1,14 @@
1
- // Generated at scaffold time by `patchLayout` (Phase 6) based on the user's
2
- // EXPO_PRIMARY_FONT / EXPO_SECONDARY_FONT answers.
3
- // @@FONTS_OBJECT@@
1
+ // Empty font registry drop matching .ttf files into `assets/fonts/` and
2
+ // fill the enum values with PostScript names (e.g. `BOLD = "Inter-Bold"`).
3
+ // Wire `useFonts(...)` in `src/app/_layout.tsx` to actually load them.
4
+ export const enum Fonts {
5
+ BOLD = "",
6
+ EXTRA_BOLD = "",
7
+ EXTRA_LIGHT = "",
8
+ MEDIUM = "",
9
+ REGULAR = "",
10
+ SEMI_BOLD = "",
11
+ ITALIC = "",
12
+ LIGHT = "",
13
+ THIN = "",
14
+ }
File without changes
File without changes
File without changes
File without changes
@@ -1,39 +0,0 @@
1
- import { StyleSheet } from "react-native";
2
- import { Colors } from "./colors";
3
- import { RF } from "./responsive";
4
-
5
- export const AllStyles = StyleSheet.create({
6
- backdropAbsoluteBlur: {
7
- ...StyleSheet.absoluteFillObject,
8
- backgroundColor: "rgba(0, 0, 0, 0.5)",
9
- shadowColor: "#000",
10
- shadowOffset: { width: 0, height: -2 },
11
- shadowOpacity: 0.25,
12
- shadowRadius: 8,
13
- elevation: 8,
14
- zIndex: 1,
15
- },
16
- shadowBackdrop: {
17
- ...StyleSheet.absoluteFillObject,
18
- backgroundColor: "rgba(0, 0, 0, 0.5)",
19
- shadowColor: "#000",
20
- shadowOffset: { width: 0, height: -2 },
21
- shadowOpacity: 0.25,
22
- shadowRadius: 8,
23
- elevation: 8,
24
- zIndex: 1,
25
- },
26
- modalBorderStyle: {
27
- borderTopLeftRadius: RF(20),
28
- borderTopRightRadius: RF(20),
29
- backgroundColor: Colors.GRAY_1,
30
- },
31
- messageListContainer: { gap: RF(16), padding: RF(16) },
32
- modalBackgroundColor: {
33
- backgroundColor: Colors.GRAY_1,
34
- },
35
- fullWidthButton: {
36
- width: "100%",
37
- },
38
- shadowOffset: { width: 0, height: 2 },
39
- });