- {/* Header */}
-
-
-
- Perwerde - Education Platform
-
-
- Decentralized learning for Digital Kurdistan. Build skills, earn credentials, empower our nation.
-
+
+
+
+
+ Perwerde - Education Platform
+
+
+ Decentralized learning for Digital Kurdistan. Build skills, earn credentials, empower our nation.
+
+
+
- {/* Stats Cards */}
- {studentProgress && (
-
-
-
-
-
-
-
-
-
{studentProgress.totalCourses}
-
Enrolled Courses
-
-
-
-
+
+
+ Available Courses
+ {selectedAccount && My Dashboard}
+ {isAdmin && Create Course}
+
-
-
-
-
-
-
-
-
{studentProgress.completedCourses}
-
Completed
-
-
-
-
+
+ e.course_id)}
+ onEnroll={handleDataChange}
+ />
+
-
-
-
-
-
-
-
-
{studentProgress.activeCourses}
-
In Progress
-
-
-
-
-
-
-
-
-
-
-
{studentProgress.totalPoints}
-
Total Points
-
-
-
-
-
- )}
-
- {/* Courses List */}
-
-
-
- {courses.length > 0 ? `Available Courses (${courses.length})` : 'No Courses Available'}
-
-
-
- {courses.length === 0 ? (
-
-
-
- No Active Courses
-
- Check back later for new educational content. Courses will be added by educators.
-
-
-
- ) : (
-
- {courses.map((course) => {
- const isUserEnrolled = enrolledCourseIds.includes(course.id);
-
- return (
-
-
-
- {/* Course Info */}
-
-
-
{course.name}
-
- #{course.id}
-
- {isUserEnrolled && (
-
- Enrolled
-
- )}
-
-
-
{course.description}
-
-
-
-
- {course.owner.slice(0, 8)}...{course.owner.slice(-6)}
-
- {course.contentLink && (
-
-
- Course Materials
-
- )}
-
-
-
- {/* Actions */}
-
- {isUserEnrolled ? (
- <>
-
-
- >
- ) : (
- <>
-
-
- >
- )}
-
-
-
-
- );
- })}
-
+ {selectedAccount && (
+
+
+
)}
-
- {/* Blockchain Features */}
-
-
-
-
- Blockchain-Powered Education
-
-
-
-
- -
-
- Decentralized course hosting (IPFS)
-
- -
-
- On-chain enrollment & completion tracking
-
- -
-
- Points-based achievement system
-
- -
-
- Trust score integration
-
- -
-
- Transparent educator verification
-
- -
-
- Immutable learning records
-
-
-
-
+ {isAdmin && (
+
+ {
+ handleDataChange();
+ setActiveTab('courses'); // Switch back to course list after creation
+ }} />
+
+ )}
+
);
}
diff --git a/web/supabase/migrations/006_create_perwerde_tables.sql b/web/supabase/migrations/006_create_perwerde_tables.sql
new file mode 100644
index 00000000..0ad0ce91
--- /dev/null
+++ b/web/supabase/migrations/006_create_perwerde_tables.sql
@@ -0,0 +1,85 @@
+-- Perwerde (Education) Tables
+-- Courses Table (ID comes from blockchain)
+CREATE TABLE IF NOT EXISTS public.courses (
+ id BIGINT PRIMARY KEY, -- Blockchain course_id
+ owner TEXT NOT NULL,
+ name TEXT NOT NULL,
+ description TEXT,
+ content_link TEXT, -- IPFS hash
+ status TEXT NOT NULL DEFAULT 'Active',
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL
+);
+
+-- Enrollments Table
+CREATE TABLE IF NOT EXISTS public.enrollments (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ student_address TEXT NOT NULL,
+ course_id BIGINT REFERENCES public.courses(id) ON DELETE CASCADE,
+ enrolled_at TIMESTAMP WITH TIME ZONE NOT NULL,
+ completed_at TIMESTAMP WITH TIME ZONE,
+ points_earned INTEGER DEFAULT 0,
+ is_completed BOOLEAN DEFAULT false,
+ UNIQUE(student_address, course_id)
+);
+
+-- Indexes
+CREATE INDEX IF NOT EXISTS idx_courses_status ON public.courses(status);
+CREATE INDEX IF NOT EXISTS idx_enrollments_student ON public.enrollments(student_address);
+CREATE INDEX IF NOT EXISTS idx_enrollments_course ON public.enrollments(course_id);
+
+-- RLS Policies
+-- Courses
+ALTER TABLE public.courses ENABLE ROW LEVEL SECURITY;
+
+DROP POLICY IF EXISTS "Anyone can view courses" ON public.courses;
+CREATE POLICY "Anyone can view courses"
+ ON public.courses FOR SELECT
+ USING (true);
+
+DROP POLICY IF EXISTS "Admins can manage courses" ON public.courses;
+CREATE POLICY "Admins can manage courses"
+ ON public.courses FOR ALL
+ USING (
+ EXISTS (
+ SELECT 1 FROM public.admin_roles
+ WHERE user_id = auth.uid()
+ )
+ );
+
+-- Enrollments
+ALTER TABLE public.enrollments ENABLE ROW LEVEL SECURITY;
+
+DROP POLICY IF EXISTS "Users can view their own enrollments" ON public.enrollments;
+CREATE POLICY "Users can view their own enrollments"
+ ON public.enrollments FOR SELECT
+ USING (
+ EXISTS (
+ SELECT 1 FROM public.wallets
+ WHERE user_id = auth.uid() AND address = student_address
+ )
+ );
+
+DROP POLICY IF EXISTS "Users can create their own enrollments" ON public.enrollments;
+CREATE POLICY "Users can create their own enrollments"
+ ON public.enrollments FOR INSERT
+ WITH CHECK (
+ EXISTS (
+ SELECT 1 FROM public.wallets
+ WHERE user_id = auth.uid() AND address = student_address
+ )
+ );
+
+DROP POLICY IF EXISTS "Admins or course owners can update enrollments" ON public.enrollments;
+CREATE POLICY "Admins or course owners can update enrollments"
+ ON public.enrollments FOR UPDATE
+ USING (
+ EXISTS (
+ SELECT 1 FROM public.admin_roles
+ WHERE user_id = auth.uid()
+ ) OR
+ EXISTS (
+ SELECT 1 FROM public.courses c
+ JOIN public.wallets w ON c.owner = w.address
+ WHERE c.id = course_id AND w.user_id = auth.uid()
+ )
+ );
diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json
index 1d91bbbf..8686500c 100644
--- a/web/tsconfig.app.json
+++ b/web/tsconfig.app.json
@@ -27,5 +27,5 @@
"@/*": ["./src/*"]
}
},
- "include": ["src"]
+ "include": ["src", "../shared"]
}
diff --git a/web/tsconfig.json b/web/tsconfig.json
index 8a02a4e9..69632d6b 100644
--- a/web/tsconfig.json
+++ b/web/tsconfig.json
@@ -12,7 +12,9 @@
"@pezkuwi/lib": ["../shared/lib"],
"@pezkuwi/utils": ["../shared/utils"],
"@pezkuwi/theme": ["../shared/theme"],
- "@pezkuwi/types": ["../shared/types"]
+ "@pezkuwi/types": ["../shared/types"],
+ "@pezkuwi/components/*": ["../shared/components/*"],
+ "@shared/*": ["../shared/*"]
},
"strict": true,
"noImplicitAny": true,
diff --git a/web/vite.config.ts b/web/vite.config.ts
index 04552643..76d81d13 100644
--- a/web/vite.config.ts
+++ b/web/vite.config.ts
@@ -31,8 +31,10 @@ export default defineConfig(({ mode }) => ({
"@pezkuwi/utils": path.resolve(__dirname, "../shared/utils"),
"@pezkuwi/theme": path.resolve(__dirname, "../shared/theme"),
"@pezkuwi/types": path.resolve(__dirname, "../shared/types"),
+ "@pezkuwi/components": path.resolve(__dirname, "../shared/components"),
+ "@shared": path.resolve(__dirname, "../shared"),
},
- dedupe: ['@polkadot/util-crypto', '@polkadot/util', '@polkadot/api', '@polkadot/extension-dapp', '@polkadot/keyring'],
+ dedupe: ['react', 'lucide-react', 'sonner', '@polkadot/util-crypto', '@polkadot/util', '@polkadot/api', '@polkadot/extension-dapp', '@polkadot/keyring'],
},
optimizeDeps: {
include: ['@polkadot/util-crypto', '@polkadot/util', '@polkadot/api', '@polkadot/extension-dapp', '@polkadot/keyring'],