Files
pwap/shared/lib/tiki.ts
T
Claude c59f5c3391 Centralize common code in shared folder
This commit reorganizes the codebase to eliminate duplication between web and mobile frontends by moving all commonly used files to the shared folder.

Changes:
- Moved lib files to shared/lib/:
  * wallet.ts, staking.ts, tiki.ts, identity.ts
  * multisig.ts, usdt.ts, scores.ts, citizenship-workflow.ts

- Moved utils to shared/utils/:
  * auth.ts, dex.ts
  * Created format.ts (extracted formatNumber from web utils)

- Created shared/theme/:
  * colors.ts (Kurdistan and App color definitions)

- Updated web configuration:
  * Added @pezkuwi/* path aliases in tsconfig.json and vite.config.ts
  * Updated all imports to use @pezkuwi/lib/*, @pezkuwi/utils/*, @pezkuwi/theme/*
  * Removed duplicate files from web/src/lib and web/src/utils

- Updated mobile configuration:
  * Added @pezkuwi/* path aliases in tsconfig.json
  * Updated theme/colors.ts to re-export from shared
  * Mobile already uses relative imports to shared (no changes needed)

Architecture Benefits:
- Single source of truth for common code
- No duplication between frontends
- Easier maintenance and consistency
- Clear separation of shared vs platform-specific code

Web-specific files kept:
- web/src/lib/supabase.ts
- web/src/lib/utils.ts (cn function for Tailwind, re-exports formatNumber from shared)

All imports updated and tested. Both web and mobile now use the centralized shared folder.
2025-11-14 22:46:39 +00:00

400 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ========================================
// Pallet-Tiki Integration
// ========================================
// This file handles all tiki-related blockchain interactions
// Based on: /Pezkuwi-SDK/pezkuwi/pallets/tiki/src/lib.rs
import type { ApiPromise } from '@polkadot/api';
// ========================================
// TIKI TYPES (from Rust enum)
// ========================================
export enum Tiki {
// Otomatik - KYC sonrası
Hemwelatî = 'Hemwelatî',
// Seçilen roller (Elected)
Parlementer = 'Parlementer',
SerokiMeclise = 'SerokiMeclise',
Serok = 'Serok',
// Atanan roller (Appointed) - Yargı
EndameDiwane = 'EndameDiwane',
Dadger = 'Dadger',
Dozger = 'Dozger',
Hiquqnas = 'Hiquqnas',
Noter = 'Noter',
// Atanan roller - Yürütme
Wezir = 'Wezir',
SerokWeziran = 'SerokWeziran',
WezireDarayiye = 'WezireDarayiye',
WezireParez = 'WezireParez',
WezireDad = 'WezireDad',
WezireBelaw = 'WezireBelaw',
WezireTend = 'WezireTend',
WezireAva = 'WezireAva',
WezireCand = 'WezireCand',
// Atanan roller - İdari
Xezinedar = 'Xezinedar',
Bacgir = 'Bacgir',
GerinendeyeCavkaniye = 'GerinendeyeCavkaniye',
OperatorêTorê = 'OperatorêTorê',
PisporêEwlehiyaSîber = 'PisporêEwlehiyaSîber',
GerinendeyeDaneye = 'GerinendeyeDaneye',
Berdevk = 'Berdevk',
Qeydkar = 'Qeydkar',
Balyoz = 'Balyoz',
Navbeynkar = 'Navbeynkar',
ParêzvaneÇandî = 'ParêzvaneÇandî',
Mufetîs = 'Mufetîs',
KalîteKontrolker = 'KalîteKontrolker',
// Atanan roller - Kültürel/Dini
Mela = 'Mela',
Feqî = 'Feqî',
Perwerdekar = 'Perwerdekar',
Rewsenbîr = 'Rewsenbîr',
RêveberêProjeyê = 'RêveberêProjeyê',
SerokêKomele = 'SerokêKomele',
ModeratorêCivakê = 'ModeratorêCivakê',
// Kazanılan roller (Earned)
Axa = 'Axa',
Pêseng = 'Pêseng',
Sêwirmend = 'Sêwirmend',
Hekem = 'Hekem',
Mamoste = 'Mamoste',
// Ekonomik rol
Bazargan = 'Bazargan',
}
// Role assignment types
export enum RoleAssignmentType {
Automatic = 'Automatic',
Appointed = 'Appointed',
Elected = 'Elected',
Earned = 'Earned',
}
// Tiki to Display Name mapping (English)
export const TIKI_DISPLAY_NAMES: Record<string, string> = {
Hemwelatî: 'Citizen',
Parlementer: 'Parliament Member',
SerokiMeclise: 'Speaker of Parliament',
Serok: 'President',
Wezir: 'Minister',
SerokWeziran: 'Prime Minister',
WezireDarayiye: 'Minister of Finance',
WezireParez: 'Minister of Defense',
WezireDad: 'Minister of Justice',
WezireBelaw: 'Minister of Education',
WezireTend: 'Minister of Health',
WezireAva: 'Minister of Water',
WezireCand: 'Minister of Culture',
EndameDiwane: 'Supreme Court Member',
Dadger: 'Judge',
Dozger: 'Prosecutor',
Hiquqnas: 'Lawyer',
Noter: 'Notary',
Xezinedar: 'Treasurer',
Bacgir: 'Tax Collector',
GerinendeyeCavkaniye: 'Resource Manager',
OperatorêTorê: 'Network Operator',
PisporêEwlehiyaSîber: 'Cybersecurity Expert',
GerinendeyeDaneye: 'Data Manager',
Berdevk: 'Representative',
Qeydkar: 'Registrar',
Balyoz: 'Ambassador',
Navbeynkar: 'Mediator',
ParêzvaneÇandî: 'Cultural Guardian',
Mufetîs: 'Inspector',
KalîteKontrolker: 'Quality Controller',
Mela: 'Religious Scholar',
Feqî: 'Religious Jurist',
Perwerdekar: 'Educator',
Rewsenbîr: 'Intellectual',
RêveberêProjeyê: 'Project Manager',
SerokêKomele: 'Community Leader',
ModeratorêCivakê: 'Community Moderator',
Axa: 'Elder',
Pêseng: 'Pioneer',
Sêwirmend: 'Advisor',
Hekem: 'Expert',
Mamoste: 'Teacher',
Bazargan: 'Merchant',
};
// Tiki scores (from get_bonus_for_tiki function)
export const TIKI_SCORES: Record<string, number> = {
Axa: 250,
RêveberêProjeyê: 250,
ModeratorêCivakê: 200,
Serok: 200,
EndameDiwane: 175,
Dadger: 150,
SerokiMeclise: 150,
SerokWeziran: 125,
Dozger: 120,
Wezir: 100,
WezireDarayiye: 100,
WezireParez: 100,
WezireDad: 100,
WezireBelaw: 100,
WezireTend: 100,
WezireAva: 100,
WezireCand: 100,
SerokêKomele: 100,
Xezinedar: 100,
PisporêEwlehiyaSîber: 100,
Parlementer: 100,
Mufetîs: 90,
Balyoz: 80,
Hiquqnas: 75,
Berdevk: 70,
Mamoste: 70,
Bazargan: 60,
OperatorêTorê: 60,
Mela: 50,
Feqî: 50,
Noter: 50,
Bacgir: 50,
Perwerdekar: 40,
Rewsenbîr: 40,
GerinendeyeCavkaniye: 40,
GerinendeyeDaneye: 40,
KalîteKontrolker: 30,
Navbeynkar: 30,
Hekem: 30,
Qeydkar: 25,
ParêzvaneÇandî: 25,
Sêwirmend: 20,
Hemwelatî: 10,
Pêseng: 5, // Default for unlisted
};
// ========================================
// ROLE CATEGORIZATION
// ========================================
export const ROLE_CATEGORIES: Record<string, string[]> = {
Government: ['Serok', 'SerokWeziran', 'Wezir', 'WezireDarayiye', 'WezireParez', 'WezireDad', 'WezireBelaw', 'WezireTend', 'WezireAva', 'WezireCand'],
Legislature: ['Parlementer', 'SerokiMeclise'],
Judiciary: ['EndameDiwane', 'Dadger', 'Dozger', 'Hiquqnas', 'Noter'],
Administration: ['Xezinedar', 'Bacgir', 'Berdevk', 'Qeydkar', 'Balyoz', 'Mufetîs'],
Technical: ['OperatorêTorê', 'PisporêEwlehiyaSîber', 'GerinendeyeDaneye', 'GerinendeyeCavkaniye'],
Cultural: ['Mela', 'Feqî', 'ParêzvaneÇandî'],
Education: ['Mamoste', 'Perwerdekar', 'Rewsenbîr'],
Community: ['SerokêKomele', 'ModeratorêCivakê', 'Axa', 'Navbeynkar', 'Sêwirmend', 'Hekem'],
Economic: ['Bazargan'],
Leadership: ['RêveberêProjeyê', 'Pêseng'],
Quality: ['KalîteKontrolker'],
Citizen: ['Hemwelatî'],
};
// ========================================
// TIKI QUERY FUNCTIONS
// ========================================
/**
* Fetch user's tiki roles from blockchain
* @param api - Polkadot API instance
* @param address - User's substrate address
* @returns Array of tiki role strings
*/
export const fetchUserTikis = async (
api: ApiPromise,
address: string
): Promise<string[]> => {
try {
if (!api || !api.query.tiki) {
console.warn('Tiki pallet not available on this chain');
return [];
}
// Query UserTikis storage
const tikis = await api.query.tiki.userTikis(address);
const tikisArray = tikis.toJSON() as any[];
if (!tikisArray || tikisArray.length === 0) {
return [];
}
// Convert from enum index to string names
return tikisArray.map((tikiIndex: any) => {
// Tikis are stored as enum variants
if (typeof tikiIndex === 'string') {
return tikiIndex;
} else if (typeof tikiIndex === 'object' && tikiIndex !== null) {
// Handle object variant format {variantName: null}
return Object.keys(tikiIndex)[0];
}
return 'Unknown';
}).filter((t: string) => t !== 'Unknown');
} catch (error) {
console.error('Error fetching user tikis:', error);
return [];
}
};
/**
* Check if user is a citizen (has Hemwelatî tiki)
* @param api - Polkadot API instance
* @param address - User's substrate address
* @returns boolean
*/
export const isCitizen = async (
api: ApiPromise,
address: string
): Promise<boolean> => {
try {
if (!api || !api.query.tiki) {
return false;
}
const citizenNft = await api.query.tiki.citizenNft(address);
return !citizenNft.isEmpty;
} catch (error) {
console.error('Error checking citizenship:', error);
return false;
}
};
/**
* Calculate total tiki score for a user
* @param tikis - Array of tiki strings
* @returns Total score
*/
export const calculateTikiScore = (tikis: string[]): number => {
return tikis.reduce((total, tiki) => {
return total + (TIKI_SCORES[tiki] || 5);
}, 0);
};
/**
* Get primary role (highest scoring) from tikis
* @param tikis - Array of tiki strings
* @returns Primary role string
*/
export const getPrimaryRole = (tikis: string[]): string => {
if (!tikis || tikis.length === 0) {
return 'Member';
}
// Find highest scoring role
let primaryRole = tikis[0];
let highestScore = TIKI_SCORES[tikis[0]] || 5;
for (const tiki of tikis) {
const score = TIKI_SCORES[tiki] || 5;
if (score > highestScore) {
highestScore = score;
primaryRole = tiki;
}
}
return primaryRole;
};
/**
* Get display name for a tiki
* @param tiki - Tiki string
* @returns Display name
*/
export const getTikiDisplayName = (tiki: string): string => {
return TIKI_DISPLAY_NAMES[tiki] || tiki;
};
/**
* Get all role categories for user's tikis
* @param tikis - Array of tiki strings
* @returns Array of category names
*/
export const getUserRoleCategories = (tikis: string[]): string[] => {
const categories = new Set<string>();
for (const tiki of tikis) {
for (const [category, roles] of Object.entries(ROLE_CATEGORIES)) {
if (roles.includes(tiki)) {
categories.add(category);
}
}
}
return Array.from(categories);
};
/**
* Check if user has a specific tiki
* @param tikis - Array of tiki strings
* @param tiki - Tiki to check
* @returns boolean
*/
export const hasTiki = (tikis: string[], tiki: string): boolean => {
return tikis.includes(tiki);
};
// ========================================
// DISPLAY HELPERS
// ========================================
/**
* Get color for a tiki role
* @param tiki - Tiki string
* @returns Tailwind color class
*/
export const getTikiColor = (tiki: string): string => {
const score = TIKI_SCORES[tiki] || 5;
if (score >= 200) return 'text-purple-500';
if (score >= 150) return 'text-pink-500';
if (score >= 100) return 'text-blue-500';
if (score >= 70) return 'text-cyan-500';
if (score >= 40) return 'text-teal-500';
if (score >= 20) return 'text-green-500';
return 'text-gray-500';
};
/**
* Get emoji icon for a tiki category
* @param tiki - Tiki string
* @returns Emoji string
*/
export const getTikiEmoji = (tiki: string): string => {
for (const [category, roles] of Object.entries(ROLE_CATEGORIES)) {
if (roles.includes(tiki)) {
switch (category) {
case 'Government': return '👑';
case 'Legislature': return '🏛️';
case 'Judiciary': return '⚖️';
case 'Administration': return '📋';
case 'Technical': return '💻';
case 'Cultural': return '📿';
case 'Education': return '👨‍🏫';
case 'Community': return '🤝';
case 'Economic': return '💰';
case 'Leadership': return '⭐';
case 'Quality': return '✅';
case 'Citizen': return '👤';
}
}
}
return '👤';
};
/**
* Get badge variant for a tiki
* @param tiki - Tiki string
* @returns Badge variant string
*/
export const getTikiBadgeVariant = (tiki: string): 'default' | 'secondary' | 'destructive' | 'outline' => {
const score = TIKI_SCORES[tiki] || 5;
if (score >= 150) return 'default'; // Purple/blue for high ranks
if (score >= 70) return 'secondary'; // Gray for mid ranks
return 'outline'; // Outline for low ranks
};