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:
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals"],
|
||||
"rules": {
|
||||
"react/no-unescaped-entities": "off"
|
||||
}
|
||||
}
|
||||
Generated
+281
-4748
File diff suppressed because it is too large
Load Diff
+11
-31
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
Reference in New Issue
Block a user