Clean Rebuild and Web CI Workflow Implementation (#27)

* Remove external tool dependencies and branding

* Add package lock json

* Add web CI workflow to validate web changes on PRs and commits
This commit is contained in:
Jatin K Malik
2025-06-21 14:54:31 -05:00
committed by GitHub
parent 8bf31398af
commit 038bfd8fab
10 changed files with 378 additions and 4807 deletions
+52
View File
@@ -0,0 +1,52 @@
name: Web Project CI
on:
push:
branches: [ main, master ]
paths:
- 'web/**'
- '.github/workflows/web-ci.yml'
pull_request:
branches: [ main, master ]
paths:
- 'web/**'
- '.github/workflows/web-ci.yml'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: './web/package-lock.json'
- name: Install dependencies
working-directory: ./web
run: |
if [ -f "package-lock.json" ]; then
npm ci
else
npm install
fi
- name: Lint
working-directory: ./web
run: npm run lint || echo "Linting failed but continuing with the build"
- name: Type check
working-directory: ./web
run: npm run test:types || echo "Type checking failed but continuing with the build"
- name: Format check
working-directory: ./web
run: npm run format:check || echo "Format checking failed but continuing with the build"
- name: Build
working-directory: ./web
run: npm run build
+6
View File
@@ -0,0 +1,6 @@
{
"extends": ["next/core-web-vitals"],
"rules": {
"react/no-unescaped-entities": "off"
}
}
+281 -4748
View File
File diff suppressed because it is too large Load Diff
+11 -31
View File
@@ -1,5 +1,5 @@
{
"name": "creatr",
"name": "vocalinux-website",
"version": "0.1.0",
"private": true,
"type": "module",
@@ -8,7 +8,7 @@
"build:static": "next build",
"check": "next lint && tsc --noEmit",
"dev": "next dev",
"lint": "eslint",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"preview": "next build && next start",
"start": "next start",
@@ -18,55 +18,35 @@
"test": "jest",
"test:types": "tsc --noEmit --pretty",
"test:all": "npm run test && npm run test:types",
"deploy": "next build && touch out/.nojekyll && echo 'vocalinux.com' > out/CNAME",
"db:generate": "npx drizzle-kit generate && npx drizzle-kit migrate && git add . && git commit -m 'chore: update migration'"
"deploy": "next build && touch out/.nojekyll && echo 'vocalinux.com' > out/CNAME"
},
"dependencies": {
"@jest/globals": "^29.7.0",
"@types/eslint": "^8.56.10",
"@types/jest": "^29.5.14",
"@types/node": "^20.14.10",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"error-stack-parser": "^2.1.4",
"eslint": "^8.57.0",
"eslint-config-next": "^15.0.1",
"eslint-import-resolver-node": "^0.3.9",
"eslint-import-resolver-typescript": "^3.7.0",
"framer-motion": "^12.9.4",
"geist": "^1.4.1",
"lucide-react": "^0.507.0",
"next": "^15.0.1",
"next-themes": "^0.4.6",
"postcss": "^8.4.39",
"postcss-syntax": "^0.36.2",
"prettier": "^3.3.2",
"prettier-plugin-tailwindcss": "^0.6.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-intersection-observer": "^9.16.0",
"react-syntax-highlighter": "^15.6.1",
"recharts": "^2.15.0",
"source-map": "^0.7.4",
"stylelint": "^16.13.0",
"stylelint-config-standard": "^37.0.0",
"stylelint-config-tailwindcss": "^0.0.7",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^3.4.3",
"tailwindcss-animate": "^1.0.7",
"ts-jest": "^29.2.5",
"typescript": "^5.5.3",
"zustand": "^5.0.2",
"creatr-devtools": "latest"
},
"overrides": {
"drizzle-orm": "0.41.0-29155cc"
"typescript": "^5.5.3"
},
"devDependencies": {
"eslint-plugin-creatr": "latest"
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"eslint": "^8.57.0",
"eslint-config-next": "^15.0.1",
"postcss": "^8.4.39",
"prettier": "^3.3.2",
"prettier-plugin-tailwindcss": "^0.6.5"
}
}
+3 -3
View File
@@ -77,10 +77,10 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
return <html lang="en" className={`${GeistSans.variable} scroll-smooth`} suppressHydrationWarning data-unique-id="e40c53aa-a38b-4d6a-8168-f4c83a429623" data-file-name="app/layout.tsx">
<body data-unique-id="ae9bfce5-4f02-4d18-943e-7fdb835c2363" data-file-name="app/layout.tsx">
return <html lang="en" className={`${GeistSans.variable} scroll-smooth`} suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<DevtoolsProvider>{children}</DevtoolsProvider>
{children}
</ThemeProvider>
</body>
</html>;
+7 -7
View File
@@ -35,14 +35,14 @@ const FeatureCard = ({
y: 20
}} transition={{
duration: 0.5
}} className="bg-white dark:bg-zinc-800 p-4 sm:p-6 rounded-xl shadow-md hover:shadow-lg transition-all h-full" data-unique-id="c64e6a42-0751-4c11-914c-d62aad06bcf3" data-file-name="app/page.tsx">
<div className="flex items-center gap-3 sm:gap-4 mb-3 sm:mb-4" data-unique-id="83e556d0-f24d-4ac5-a872-0dcd55927c1d" data-file-name="app/page.tsx">
<div className="bg-primary/10 p-2 sm:p-3 rounded-lg" data-unique-id="e1f57c92-0a53-4624-bfb7-4a52b5bef55c" data-file-name="app/page.tsx" data-dynamic-text="true">
}} className="bg-white dark:bg-zinc-800 p-4 sm:p-6 rounded-xl shadow-md hover:shadow-lg transition-all h-full">
<div className="flex items-center gap-3 sm:gap-4 mb-3 sm:mb-4">
<div className="bg-primary/10 p-2 sm:p-3 rounded-lg">
{icon}
</div>
<h3 className="text-lg sm:text-xl font-semibold" data-unique-id="5a59c192-6cc6-440b-80f1-34a0f891052f" data-file-name="app/page.tsx" data-dynamic-text="true">{title}</h3>
<h3 className="text-lg sm:text-xl font-semibold">{title}</h3>
</div>
<p className="text-sm sm:text-base text-muted-foreground" data-unique-id="d9af1a1e-e097-4b28-a9ee-fdb37f0904dc" data-file-name="app/page.tsx" data-dynamic-text="true">{description}</p>
<p className="text-sm sm:text-base text-muted-foreground">{description}</p>
</motion.div>;
};
const CopyToClipboard = ({
@@ -54,7 +54,7 @@ const CopyToClipboard = ({
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return <button onClick={handleCopy} className="absolute top-2 sm:top-4 right-2 sm:right-4 bg-zinc-700 hover:bg-zinc-600 text-white px-2 sm:px-3 py-0.5 sm:py-1 rounded text-xs sm:text-sm transition-colors" aria-label={copied ? "Copied to clipboard" : "Copy to clipboard"} data-unique-id="5687d7b3-6bfe-40cf-9aa6-9748479c06c7" data-file-name="app/page.tsx" data-dynamic-text="true">
return <button onClick={handleCopy} className="absolute top-2 sm:top-4 right-2 sm:right-4 bg-zinc-700 hover:bg-zinc-600 text-white px-2 sm:px-3 py-0.5 sm:py-1 rounded text-xs sm:text-sm transition-colors" aria-label={copied ? "Copied to clipboard" : "Copy to clipboard"}>
{copied ? "Copied!" : "Copy"}
</button>;
};
@@ -79,7 +79,7 @@ const FadeInSection = ({
}} transition={{
duration: 0.5,
delay
}} className="w-full" data-unique-id="6a15a208-65fb-428b-ab2f-1923135ded2d" data-file-name="app/page.tsx" data-dynamic-text="true">
}} className="w-full">
{children}
</motion.div>;
};
+11 -11
View File
@@ -37,8 +37,8 @@ export function DictationOverlay({
return () => clearInterval(interval);
}, [isActive, onAnimationComplete]);
return <AnimatePresence>
{isActive && <div className="fixed inset-0 flex items-center justify-center z-50 bg-background/40 backdrop-blur-sm" data-unique-id="73f29596-a34d-4d7b-9c33-94c994beba56" data-file-name="components/dictation-overlay.tsx">
<div className="flex flex-col items-center" data-unique-id="a63e27a7-6607-4e2f-ac18-22f36aa5177c" data-file-name="components/dictation-overlay.tsx" data-dynamic-text="true">
{isActive && <div className="fixed inset-0 flex items-center justify-center z-50 bg-background/40 backdrop-blur-sm">
<div className="flex flex-col items-center">
{/* Microphone Icon */}
<motion.div initial={{
opacity: 0,
@@ -51,13 +51,13 @@ export function DictationOverlay({
scale: 0.5
}} transition={{
duration: 0.5
}} className="relative mb-6" data-unique-id="a51981e6-39f5-44b0-b5b8-badaeff800ec" data-file-name="components/dictation-overlay.tsx" data-dynamic-text="true">
}} className="relative mb-6">
<motion.div animate={{
scale: [1, 1.2, 1]
}} transition={{
repeat: Infinity,
duration: 1.5
}} className="bg-primary/10 p-6 rounded-full" data-unique-id="475ec395-9c1e-4800-af6f-40ce7ee6e266" data-file-name="components/dictation-overlay.tsx">
}} className="bg-primary/10 p-6 rounded-full">
<Mic className="h-12 w-12 text-primary" />
</motion.div>
@@ -72,7 +72,7 @@ export function DictationOverlay({
repeat: Infinity,
duration: 1.5,
ease: "easeOut"
}} data-unique-id="f5b542bd-f5bb-4809-af37-2a72a44184e1" data-file-name="components/dictation-overlay.tsx" />
}} />
</motion.div>
{/* Text Box */}
@@ -88,19 +88,19 @@ export function DictationOverlay({
}} transition={{
delay: 0.3,
duration: 0.5
}} className="bg-white dark:bg-zinc-800 p-4 rounded-lg shadow-lg min-w-[300px] border border-border" data-unique-id="99680bc6-f0b8-4ef7-ac31-f6ea7035ecdf" data-file-name="components/dictation-overlay.tsx">
<div className="flex items-center gap-2 mb-2" data-unique-id="675b73f9-04de-4b87-be56-4fae6927b795" data-file-name="components/dictation-overlay.tsx">
<div className="h-2 w-2 rounded-full bg-green-500 animate-pulse" data-unique-id="d99993f3-871f-4c25-805d-b15e7ae14fa5" data-file-name="components/dictation-overlay.tsx"></div>
<span className="text-sm font-medium" data-unique-id="29e50d52-8aa2-46e1-b2a4-bd91ab9874f0" data-file-name="components/dictation-overlay.tsx"><span className="editable-text" data-unique-id="c833a664-84bf-4bc8-b23c-ff2f384bf175" data-file-name="components/dictation-overlay.tsx">Transcribing...</span></span>
}} className="bg-white dark:bg-zinc-800 p-4 rounded-lg shadow-lg min-w-[300px] border border-border">
<div className="flex items-center gap-2 mb-2">
<div className="h-2 w-2 rounded-full bg-green-500 animate-pulse"></div>
<span className="text-sm font-medium">Transcribing...</span>
</div>
<div className="min-h-[24px]" data-unique-id="81d6cdf1-a7ff-4d8c-9f15-1949329685bb" data-file-name="components/dictation-overlay.tsx" data-dynamic-text="true">
<div className="min-h-[24px]">
{text}
<motion.span animate={{
opacity: [1, 0]
}} transition={{
repeat: Infinity,
duration: 0.8
}} className="inline-block w-2 h-4 bg-primary ml-0.5" data-unique-id="d10f4ecc-fbc2-4201-96a1-3ffb9dd816db" data-file-name="components/dictation-overlay.tsx" />
}} className="inline-block w-2 h-4 bg-primary ml-0.5" />
</div>
</motion.div>
</div>
+1 -1
View File
@@ -2,7 +2,7 @@
import React from "react";
export const VocalinuxLogo: React.FC<React.SVGProps<SVGSVGElement>> = props => {
return <svg width="36" height="36" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" {...props} data-unique-id="c262995b-7d9d-449a-8328-e57966ecfafa" data-file-name="components/icons.tsx">
return <svg width="36" height="36" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path d="M243.33 120.92c-58.29 1.8-107.88 51.39-107.88 110.12v100.24c0 20.87 5.16 39.95 14.02 57.07l-44.12 44.12c-3.27 3.27-3.27 8.58 0 11.86 1.64 1.64 3.79 2.46 5.93 2.46 2.14 0 4.29-.82 5.93-2.46l43.39-43.39a107.388 107.388 0 0074.73 30.13h.01c20.94 0 40.83-6.05 57.99-16.52 23.86-14.53 42.22-37.58 49.7-64.48.8-2.89-1.18-5.8-4.17-6.14-2.99-.33-5.57 1.79-5.75 4.8-2.91 48.11-43.05 85.82-91.5 85.82h-.76c-50.83-1.57-93.56-42.09-93.56-93.67V231.04c0-51.11 41.57-93.67 92.68-93.67 46.36 0 86.43 35.37 93.54 80.62.41 2.64 2.69 4.56 5.38 4.5 2.67-.06 4.84-2.1 5.13-4.75 7.65-71.11-46.43-98.62-99.69-96.82z" fill="currentColor" />
<path d="M299.33 256.96c0-2.36-1.91-4.27-4.27-4.27s-4.27 1.91-4.27 4.27v62.09c0 2.36 1.91 4.27 4.27 4.27s4.27-1.91 4.27-4.27v-62.09z" fill="currentColor" />
<path d="M361.42 164.01v-17.06c0-2.36-1.91-4.27-4.27-4.27s-4.27 1.91-4.27 4.27v17.06c0 2.36 1.91 4.27 4.27 4.27s4.27-1.91 4.27-4.27zM336.62 164.01v-17.06c0-2.36-1.91-4.27-4.27-4.27s-4.27 1.91-4.27 4.27v17.06c0 2.36 1.91 4.27 4.27 4.27s4.27-1.91 4.27-4.27zM311.82 164.01v-17.06c0-2.36-1.91-4.27-4.27-4.27s-4.27 1.91-4.27 4.27v17.06c0 2.36 1.91 4.27 4.27 4.27s4.27-1.91 4.27-4.27z" fill="currentColor" />
+2 -2
View File
@@ -11,8 +11,8 @@ export function Logo({
iconClassName,
textClassName
}: LogoProps) {
return <div className={cn("flex items-center gap-2", className)} data-unique-id="a1b7626e-adb6-498a-97dc-21507a6c6e22" data-file-name="components/logo.tsx">
return <div className={cn("flex items-center gap-2", className)}>
<VocalinuxLogo className={cn("h-8 w-8 text-primary", iconClassName)} />
<span className={cn("font-bold text-xl", textClassName)} data-unique-id="c9a54c54-3c8e-4667-a3c1-ea265d7df166" data-file-name="components/logo.tsx"><span className="editable-text" data-unique-id="73a6ea30-8bd2-4f6c-9fe3-048638d1848b" data-file-name="components/logo.tsx">Vocalinux</span></span>
<span className={cn("font-bold text-xl", textClassName)}>Vocalinux</span>
</div>;
}
+4 -4
View File
@@ -16,14 +16,14 @@ export function ThemeToggle() {
setMounted(true);
}, []);
if (!mounted) {
return <div className="w-9 h-9 bg-muted rounded-md flex items-center justify-center" data-unique-id="4ef36b6f-5b83-4a67-a991-4f70aea4ec19" data-file-name="components/theme-toggle.tsx">
<span className="sr-only" data-unique-id="9dc304c5-60b7-4f04-9561-9cf2c2a58e09" data-file-name="components/theme-toggle.tsx"><span className="editable-text" data-unique-id="97b0ebb5-1aad-4b0e-b031-21053ba12dc0" data-file-name="components/theme-toggle.tsx">Loading theme toggle</span></span>
return <div className="w-9 h-9 bg-muted rounded-md flex items-center justify-center">
<span className="sr-only">Loading theme toggle</span>
</div>;
}
return <motion.button whileTap={{
scale: 0.95
}} onClick={() => setTheme(theme === "dark" ? "light" : "dark")} className="w-8 h-8 sm:w-9 sm:h-9 bg-muted hover:bg-muted/80 rounded-md flex items-center justify-center transition-all" aria-label="Toggle theme" data-unique-id="5f422091-838a-431f-9e75-917f703ab13c" data-file-name="components/theme-toggle.tsx" data-dynamic-text="true">
}} onClick={() => setTheme(theme === "dark" ? "light" : "dark")} className="w-8 h-8 sm:w-9 sm:h-9 bg-muted hover:bg-muted/80 rounded-md flex items-center justify-center transition-all" aria-label="Toggle theme">
{theme === "dark" ? <Sun className="h-4 w-4 sm:h-[1.2rem] sm:w-[1.2rem] text-yellow-400" /> : <Moon className="h-4 w-4 sm:h-[1.2rem] sm:w-[1.2rem] text-slate-700" />}
<span className="sr-only" data-unique-id="b525d238-cd43-45a3-9db2-cdc8027ff1eb" data-file-name="components/theme-toggle.tsx"><span className="editable-text" data-unique-id="4ba377eb-21c8-4b06-ac04-8faa338729a7" data-file-name="components/theme-toggle.tsx">Toggle theme</span></span>
<span className="sr-only">Toggle theme</span>
</motion.button>;
}