Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 docs/design_doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ YOHAKU は、スマホ利用に「契約」と「金銭コミットメント(

アプリ起動時に Supabase の active 契約を確認し、存在する場合は Dashboard に遷移します。

### 5.4 契約フローと金額選択

- 画面遷移: `PickApps -> CreateContract -> Payment -> ConfirmContract -> Dashboard`
- 日次ペナルティは `500〜2000` 円を `100` 円刻みで選択
- デポジット総額は `penalty_per_day * 7` を利用
- 最終確認画面で決済成功後に契約を作成

## 6. 現時点で対象外

- Sign in with Apple の本番運用
Expand Down
3 changes: 2 additions & 1 deletion mobile/__tests__/app-context.contract-flow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@

useEffect(() => {
if (!profile || selectedApps.length === 0) return;
void createContract(5400).then(onCreated);
void createContract(5400, 500).then(onCreated);

Check warning on line 117 in mobile/__tests__/app-context.contract-flow.test.tsx

View workflow job for this annotation

GitHub Actions / Mobile (lint, typecheck, test)

Expected 'undefined' and instead saw 'void'
}, [createContract, onCreated, profile, selectedApps]);

return null;
Expand All @@ -136,6 +136,7 @@
startAt: '2026-02-20T00:00:00.000Z',
endAt: '2026-02-27T00:00:00.000Z',
dailyLimitSeconds: 5400,
penaltyPerDay: 500,
depositTotal: 3500,
status: 'active',
selectedApps: [MOCK_APP_CATALOG[0]],
Expand Down
3 changes: 2 additions & 1 deletion mobile/__tests__/app-context.notifications.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
didSimulateRef.current = true;

const app = selectedApps[0];
void createContract(3600).then(() => {
void createContract(3600, 500).then(() => {

Check warning on line 139 in mobile/__tests__/app-context.notifications.test.tsx

View workflow job for this annotation

GitHub Actions / Mobile (lint, typecheck, test)

Expected 'undefined' and instead saw 'void'
simulateUsage(app.bundleId, 1800);
});
}, [profile, selectedApps, setSelectedApps, createContract, simulateUsage]);
Expand All @@ -160,6 +160,7 @@
startAt: '2026-02-20T00:00:00.000Z',
endAt: '2026-02-27T00:00:00.000Z',
dailyLimitSeconds: 3600,
penaltyPerDay: 500,
depositTotal: 3500,
status: 'active',
selectedApps: [MOCK_APP_CATALOG[0]],
Expand Down
2 changes: 1 addition & 1 deletion mobile/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2949,7 +2949,7 @@ SPEC CHECKSUMS:
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
hermes-engine: 8642d8f14a548ab718ec112e9bebdfdd154138b5
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
RCT-Folly: 59ec0ac1f2f39672a0c6e6cecdd39383b764646f
RCTDeprecation: 22bf66112da540a7d40e536366ddd8557934fca1
RCTRequired: a0ed4dc41b35f79fbb6d8ba320e06882a8c792cf
RCTTypeSafety: 59a046ff1e602409a86b89fcd6edff367a5b14af
Expand Down
35 changes: 26 additions & 9 deletions mobile/src/lib/app-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,22 @@
setPaymentCompleted: (completed: boolean) => void;
pendingContractData: {
dailyLimitSeconds: number;
penaltyPerDay: number;
depositTotal: number;
} | null;
setPendingContractData: (data: {
dailyLimitSeconds: number;
depositTotal: number;
}) => void;
createContract: (dailyLimitSeconds: number) => Promise<boolean>;
setPendingContractData: (
data: {
dailyLimitSeconds: number;
penaltyPerDay: number;
depositTotal: number;
} | null,
) => void;
pendingPaymentMethodId: string | null;
setPendingPaymentMethodId: (paymentMethodId: string | null) => void;
createContract: (
dailyLimitSeconds: number,
penaltyPerDay: number,
) => Promise<boolean>;
activeContract: Contract | null;
refreshContract: () => void;
simulateUsage: (bundleId: string, seconds: number) => void;
Expand Down Expand Up @@ -90,9 +99,13 @@
const [paymentCompleted, setPaymentCompleted] = useState(false);
const [pendingContractData, setPendingContractData] = useState<{
dailyLimitSeconds: number;
penaltyPerDay: number;
depositTotal: number;
} | null>(null);
const [pendingPaymentMethodId, setPendingPaymentMethodId] = useState<
string | null
>(null);
// eslint-disable-next-line @typescript-eslint/no-unused-vars

Check warning on line 108 in mobile/src/lib/app-context.tsx

View workflow job for this annotation

GitHub Actions / Mobile (lint, typecheck, test)

'@typescript-eslint/no-unused-vars' rule is disabled but never reported
const [_, setRefreshKey] = useState(0);

const syncStepFromMockState = useCallback(async (userId?: string) => {
Expand Down Expand Up @@ -216,7 +229,7 @@
}, [ensureAuthenticatedProfile, syncStepFromMockState]);

useEffect(() => {
void login().catch(error => {

Check warning on line 232 in mobile/src/lib/app-context.tsx

View workflow job for this annotation

GitHub Actions / Mobile (lint, typecheck, test)

Expected 'undefined' and instead saw 'void'
console.error('Failed to initialize auth session:', error);
setProfile(null);
setStep('login');
Expand All @@ -225,7 +238,7 @@
}, [login]);

const logout = useCallback(() => {
void supabase.auth.signOut().catch(error => {

Check warning on line 241 in mobile/src/lib/app-context.tsx

View workflow job for this annotation

GitHub Actions / Mobile (lint, typecheck, test)

Expected 'undefined' and instead saw 'void'
console.warn('Failed to sign out from Supabase:', error);
});
mockStore.logout();
Expand All @@ -235,6 +248,7 @@
setActiveContract(null);
setPaymentCompleted(false);
setPendingContractData(null);
setPendingPaymentMethodId(null);
setStep('login');
}, []);

Expand All @@ -250,14 +264,13 @@
}, []);

const createContract = useCallback(
async (dailyLimitSeconds: number) => {
async (dailyLimitSeconds: number, penaltyPerDay: number) => {
if (selectedApps.length === 0) {
console.error('[createContract] No selected apps');
return false;
}

// Calculate deposit total (500 yen/day * 7 days)
const depositTotal = 500 * 7;
const depositTotal = penaltyPerDay * 7;

try {
await ensureAuthenticatedProfile();
Expand All @@ -281,6 +294,7 @@
},
body: JSON.stringify({
dailyLimitSeconds,
penaltyPerDay,
depositTotal,
selectedApps: selectedApps.map(app => ({
bundleId: app.bundleId,
Expand Down Expand Up @@ -309,7 +323,7 @@
startAt: contract.startAt,
endAt: contract.endAt,
dailyLimitSeconds: contract.dailyLimitSeconds,
penaltyPerDay: 500,
penaltyPerDay: contract.penaltyPerDay,
depositTotal: contract.depositTotal,
status: contract.status,
selectedApps: contract.selectedApps,
Expand Down Expand Up @@ -373,6 +387,7 @@
setRefreshKey(k => k + 1);
setPaymentCompleted(false);
setPendingContractData(null);
setPendingPaymentMethodId(null);
setStep('dashboard');
return true;
} catch (error) {
Expand Down Expand Up @@ -460,7 +475,7 @@
mockStore.simulateUsage(bundleId, seconds);
const contract = mockStore.getActiveContract();
if (contract) {
void notifyIfThresholdReached({

Check warning on line 478 in mobile/src/lib/app-context.tsx

View workflow job for this annotation

GitHub Actions / Mobile (lint, typecheck, test)

Expected 'undefined' and instead saw 'void'
contractId: contract.id,
localDate: mockStore.getMockLocalDate(),
usageSeconds: mockStore.getTodayTotalUsage(),
Expand Down Expand Up @@ -569,6 +584,8 @@
setPaymentCompleted,
pendingContractData,
setPendingContractData,
pendingPaymentMethodId,
setPendingPaymentMethodId,
createContract,
activeContract,
refreshContract,
Expand Down
3 changes: 1 addition & 2 deletions mobile/src/lib/mock-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
// Helper to generate UUIDs (simple implementation for React Native)
function generateId(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = (Math.random() * 16) | 0;

Check warning on line 89 in mobile/src/lib/mock-store.ts

View workflow job for this annotation

GitHub Actions / Mobile (lint, typecheck, test)

Unexpected use of '|'
const v = c === 'x' ? r : (r & 0x3) | 0x8;

Check warning on line 90 in mobile/src/lib/mock-store.ts

View workflow job for this annotation

GitHub Actions / Mobile (lint, typecheck, test)

Unexpected use of '&'

Check warning on line 90 in mobile/src/lib/mock-store.ts

View workflow job for this annotation

GitHub Actions / Mobile (lint, typecheck, test)

Unexpected use of '|'
return v.toString(16);
});
}
Expand Down Expand Up @@ -222,7 +222,7 @@
return this.contracts[this.contracts.length - 1];
}

createContract(dailyLimitSeconds: number): Contract {
createContract(dailyLimitSeconds: number, penaltyPerDay: number): Contract {
const existing = this.getActiveContract();
if (existing) throw new Error('Active contract already exists');
if (!this.profile) throw new Error('Not logged in');
Expand All @@ -231,7 +231,6 @@
const endAt = new Date(now);
endAt.setDate(endAt.getDate() + 7);

const penaltyPerDay = 500;
const depositTotal = penaltyPerDay * 7;

const contract: Contract = {
Expand Down
6 changes: 6 additions & 0 deletions mobile/src/navigation/AppNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PermissionScreen } from '../screens/PermissionScreen';
import { PickAppsScreen } from '../screens/PickAppsScreen';
import { PaymentScreen } from '../screens/PaymentScreen';
import { CreateContractScreen } from '../screens/CreateContractScreen';
import { ConfirmContractScreen } from '../screens/ConfirmContractScreen';
import { DashboardScreen } from '../screens/DashboardScreen';
import { RootStackParamList } from './types';
import { useApp } from '../lib/app-context';
Expand All @@ -25,6 +26,7 @@ const STEP_TO_SCREEN: Record<AppStep, keyof RootStackParamList> = {
'pick-apps': 'PickApps',
payment: 'Payment',
'create-contract': 'CreateContract',
'confirm-contract': 'ConfirmContract',
dashboard: 'Dashboard',
};

Expand Down Expand Up @@ -72,6 +74,10 @@ export function AppNavigator(): React.JSX.Element {
<Stack.Screen name="PickApps" component={PickAppsScreen} />
<Stack.Screen name="Payment" component={PaymentScreen} />
<Stack.Screen name="CreateContract" component={CreateContractScreen} />
<Stack.Screen
name="ConfirmContract"
component={ConfirmContractScreen}
/>
<Stack.Screen name="Dashboard" component={DashboardScreen} />
</Stack.Navigator>
</NavigationContainer>
Expand Down
1 change: 1 addition & 0 deletions mobile/src/navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export type RootStackParamList = {
PickApps: undefined;
Payment: undefined;
CreateContract: undefined;
ConfirmContract: undefined;
Dashboard: undefined;
};
Loading
Loading