Listen to and share the music in the Atmosphere. musicsky.up.railway.app/
nextjs atproto music typescript react
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: integrate Prettier, make checks stricter

+184 -94
+2 -2
.lintstagedrc
··· 1 1 { 2 - "*": "prettier --ignore-unknown --write", 3 - "*.{ts,tsx}": "eslint --fix" 2 + "*.{ts,tsx}": "eslint --fix", 3 + "*": "prettier --ignore-unknown --write" 4 4 }
+6
.prettierignore
··· 1 + .next 2 + out 3 + build 4 + node_modules 5 + src/lib/lexicons 6 + lexicons
+8
.prettierrc
··· 1 + { 2 + "semi": true, 3 + "singleQuote": false, 4 + "trailingComma": "all", 5 + "printWidth": 80, 6 + "tabWidth": 2, 7 + "useTabs": false 8 + }
+24 -3
eslint.config.mjs
··· 1 1 import { defineConfig, globalIgnores } from "eslint/config"; 2 2 import nextVitals from "eslint-config-next/core-web-vitals"; 3 3 import nextTs from "eslint-config-next/typescript"; 4 + import eslintConfigPrettier from "eslint-config-prettier/flat"; 4 5 5 6 const eslintConfig = defineConfig([ 6 7 ...nextVitals, 7 8 ...nextTs, 8 9 { 9 10 rules: { 10 - "id-length": ["error", { min: 3, exceptions: ["_", "i", "j"] }], 11 + "id-length": ["error", { min: 2, exceptions: ["_", "i", "j"] }], 12 + "func-style": ["error", "declaration", { allowArrowFunctions: true }], 13 + "@typescript-eslint/consistent-type-imports": [ 14 + "error", 15 + { prefer: "type-imports", fixStyle: "inline-type-imports" }, 16 + ], 17 + "@typescript-eslint/no-explicit-any": "error", 18 + "@typescript-eslint/no-unused-vars": [ 19 + "error", 20 + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, 21 + ], 22 + "no-console": ["error", { allow: ["error"] }], 23 + eqeqeq: ["error", "always"], 24 + "no-var": "error", 25 + "prefer-const": "error", 26 + "no-nested-ternary": "error", 11 27 }, 12 28 }, 13 - // Override default ignores of eslint-config-next. 14 29 globalIgnores([ 15 - // Default ignores of eslint-config-next: 16 30 ".next/**", 17 31 "out/**", 18 32 "build/**", 19 33 "next-env.d.ts", 34 + "src/lib/lexicons/**", 20 35 ]), 36 + eslintConfigPrettier, 37 + { 38 + rules: { 39 + "arrow-body-style": ["error", "as-needed"], 40 + }, 41 + }, 21 42 ]); 22 43 23 44 export default eslintConfig;
+34
package-lock.json
··· 35 35 "babel-plugin-react-compiler": "1.0.0", 36 36 "eslint": "^9", 37 37 "eslint-config-next": "16.1.6", 38 + "eslint-config-prettier": "^10.1.8", 38 39 "husky": "^9.1.7", 39 40 "lint-staged": "^16.2.7", 41 + "prettier": "^3.8.1", 40 42 "shadcn": "^3.8.5", 41 43 "tailwindcss": "^4", 42 44 "tw-animate-css": "^1.4.0", ··· 6662 6664 "url": "https://github.com/sponsors/sindresorhus" 6663 6665 } 6664 6666 }, 6667 + "node_modules/eslint-config-prettier": { 6668 + "version": "10.1.8", 6669 + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", 6670 + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", 6671 + "dev": true, 6672 + "license": "MIT", 6673 + "bin": { 6674 + "eslint-config-prettier": "bin/cli.js" 6675 + }, 6676 + "funding": { 6677 + "url": "https://opencollective.com/eslint-config-prettier" 6678 + }, 6679 + "peerDependencies": { 6680 + "eslint": ">=7.0.0" 6681 + } 6682 + }, 6665 6683 "node_modules/eslint-import-resolver-node": { 6666 6684 "version": "0.3.9", 6667 6685 "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", ··· 10274 10292 "license": "MIT", 10275 10293 "engines": { 10276 10294 "node": ">= 0.8.0" 10295 + } 10296 + }, 10297 + "node_modules/prettier": { 10298 + "version": "3.8.1", 10299 + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", 10300 + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", 10301 + "dev": true, 10302 + "license": "MIT", 10303 + "bin": { 10304 + "prettier": "bin/prettier.cjs" 10305 + }, 10306 + "engines": { 10307 + "node": ">=14" 10308 + }, 10309 + "funding": { 10310 + "url": "https://github.com/prettier/prettier?sponsor=1" 10277 10311 } 10278 10312 }, 10279 10313 "node_modules/pretty-ms": {
+5
package.json
··· 7 7 "build": "next build", 8 8 "start": "next start", 9 9 "lint": "eslint", 10 + "lint:fix": "eslint --fix", 11 + "format": "prettier --write .", 12 + "format:check": "prettier --check .", 10 13 "typecheck": "tsc", 11 14 "prepare": "husky" 12 15 }, ··· 38 41 "babel-plugin-react-compiler": "1.0.0", 39 42 "eslint": "^9", 40 43 "eslint-config-next": "16.1.6", 44 + "eslint-config-prettier": "^10.1.8", 41 45 "husky": "^9.1.7", 42 46 "lint-staged": "^16.2.7", 47 + "prettier": "^3.8.1", 43 48 "shadcn": "^3.8.5", 44 49 "tailwindcss": "^4", 45 50 "tw-animate-css": "^1.4.0",
+3 -1
src/app/auth/login/page.tsx
··· 63 63 id="handle" 64 64 type="text" 65 65 value={handle} 66 - onChange={(event) => setHandle(event.target.value)} 66 + onChange={(event) => { 67 + setHandle(event.target.value); 68 + }} 67 69 placeholder="user.example.com" 68 70 disabled={loading} 69 71 />
+1 -1
src/app/oauth/callback/route.ts
··· 1 - import { NextRequest, NextResponse } from "next/server"; 1 + import { type NextRequest, NextResponse } from "next/server"; 2 2 import { getOAuthClient } from "@/lib/auth/client"; 3 3 4 4 const PUBLIC_URL = process.env.PUBLIC_URL || "http://127.0.0.1:3000";
+1 -1
src/app/oauth/login/route.ts
··· 1 - import { NextRequest, NextResponse } from "next/server"; 1 + import { type NextRequest, NextResponse } from "next/server"; 2 2 import { getOAuthClient, SCOPE } from "@/lib/auth/client"; 3 3 4 4 export async function POST(request: NextRequest) {
+15 -3
src/components/theme-toggle.tsx
··· 24 24 </Button> 25 25 </DropdownMenuTrigger> 26 26 <DropdownMenuContent align="end"> 27 - <DropdownMenuItem onClick={() => setTheme("light")}> 27 + <DropdownMenuItem 28 + onClick={() => { 29 + setTheme("light"); 30 + }} 31 + > 28 32 Light 29 33 </DropdownMenuItem> 30 - <DropdownMenuItem onClick={() => setTheme("dark")}> 34 + <DropdownMenuItem 35 + onClick={() => { 36 + setTheme("dark"); 37 + }} 38 + > 31 39 Dark 32 40 </DropdownMenuItem> 33 - <DropdownMenuItem onClick={() => setTheme("system")}> 41 + <DropdownMenuItem 42 + onClick={() => { 43 + setTheme("system"); 44 + }} 45 + > 34 46 System 35 47 </DropdownMenuItem> 36 48 </DropdownMenuContent>
+10 -10
src/components/ui/button.tsx
··· 1 - import * as React from "react" 2 - import { cva, type VariantProps } from "class-variance-authority" 3 - import { Slot } from "radix-ui" 1 + import * as React from "react"; 2 + import { cva, type VariantProps } from "class-variance-authority"; 3 + import { Slot } from "radix-ui"; 4 4 5 - import { cn } from "@/lib/utils" 5 + import { cn } from "@/lib/utils"; 6 6 7 7 const buttonVariants = cva( 8 8 "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", ··· 35 35 variant: "default", 36 36 size: "default", 37 37 }, 38 - } 39 - ) 38 + }, 39 + ); 40 40 41 41 function Button({ 42 42 className, ··· 46 46 ...props 47 47 }: React.ComponentProps<"button"> & 48 48 VariantProps<typeof buttonVariants> & { 49 - asChild?: boolean 49 + asChild?: boolean; 50 50 }) { 51 - const Comp = asChild ? Slot.Root : "button" 51 + const Comp = asChild ? Slot.Root : "button"; 52 52 53 53 return ( 54 54 <Comp ··· 58 58 className={cn(buttonVariants({ variant, size, className }))} 59 59 {...props} 60 60 /> 61 - ) 61 + ); 62 62 } 63 63 64 - export { Button, buttonVariants } 64 + export { Button, buttonVariants };
+13 -13
src/components/ui/card.tsx
··· 1 - import * as React from "react" 1 + import * as React from "react"; 2 2 3 - import { cn } from "@/lib/utils" 3 + import { cn } from "@/lib/utils"; 4 4 5 5 function Card({ className, ...props }: React.ComponentProps<"div">) { 6 6 return ( ··· 8 8 data-slot="card" 9 9 className={cn( 10 10 "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", 11 - className 11 + className, 12 12 )} 13 13 {...props} 14 14 /> 15 - ) 15 + ); 16 16 } 17 17 18 18 function CardHeader({ className, ...props }: React.ComponentProps<"div">) { ··· 21 21 data-slot="card-header" 22 22 className={cn( 23 23 "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", 24 - className 24 + className, 25 25 )} 26 26 {...props} 27 27 /> 28 - ) 28 + ); 29 29 } 30 30 31 31 function CardTitle({ className, ...props }: React.ComponentProps<"div">) { ··· 35 35 className={cn("leading-none font-semibold", className)} 36 36 {...props} 37 37 /> 38 - ) 38 + ); 39 39 } 40 40 41 41 function CardDescription({ className, ...props }: React.ComponentProps<"div">) { ··· 45 45 className={cn("text-muted-foreground text-sm", className)} 46 46 {...props} 47 47 /> 48 - ) 48 + ); 49 49 } 50 50 51 51 function CardAction({ className, ...props }: React.ComponentProps<"div">) { ··· 54 54 data-slot="card-action" 55 55 className={cn( 56 56 "col-start-2 row-span-2 row-start-1 self-start justify-self-end", 57 - className 57 + className, 58 58 )} 59 59 {...props} 60 60 /> 61 - ) 61 + ); 62 62 } 63 63 64 64 function CardContent({ className, ...props }: React.ComponentProps<"div">) { ··· 68 68 className={cn("px-6", className)} 69 69 {...props} 70 70 /> 71 - ) 71 + ); 72 72 } 73 73 74 74 function CardFooter({ className, ...props }: React.ComponentProps<"div">) { ··· 78 78 className={cn("flex items-center px-6 [.border-t]:pt-6", className)} 79 79 {...props} 80 80 /> 81 - ) 81 + ); 82 82 } 83 83 84 84 export { ··· 89 89 CardAction, 90 90 CardDescription, 91 91 CardContent, 92 - } 92 + };
+38 -38
src/components/ui/field.tsx
··· 1 - "use client" 1 + "use client"; 2 2 3 - import { useMemo } from "react" 4 - import { cva, type VariantProps } from "class-variance-authority" 3 + import { useMemo } from "react"; 4 + import { cva, type VariantProps } from "class-variance-authority"; 5 5 6 - import { cn } from "@/lib/utils" 7 - import { Label } from "@/components/ui/label" 8 - import { Separator } from "@/components/ui/separator" 6 + import { cn } from "@/lib/utils"; 7 + import { Label } from "@/components/ui/label"; 8 + import { Separator } from "@/components/ui/separator"; 9 9 10 10 function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { 11 11 return ( ··· 14 14 className={cn( 15 15 "flex flex-col gap-6", 16 16 "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", 17 - className 17 + className, 18 18 )} 19 19 {...props} 20 20 /> 21 - ) 21 + ); 22 22 } 23 23 24 24 function FieldLegend({ ··· 34 34 "mb-3 font-medium", 35 35 "data-[variant=legend]:text-base", 36 36 "data-[variant=label]:text-sm", 37 - className 37 + className, 38 38 )} 39 39 {...props} 40 40 /> 41 - ) 41 + ); 42 42 } 43 43 44 44 function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { ··· 47 47 data-slot="field-group" 48 48 className={cn( 49 49 "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4", 50 - className 50 + className, 51 51 )} 52 52 {...props} 53 53 /> 54 - ) 54 + ); 55 55 } 56 56 57 57 const fieldVariants = cva( ··· 75 75 defaultVariants: { 76 76 orientation: "vertical", 77 77 }, 78 - } 79 - ) 78 + }, 79 + ); 80 80 81 81 function Field({ 82 82 className, ··· 91 91 className={cn(fieldVariants({ orientation }), className)} 92 92 {...props} 93 93 /> 94 - ) 94 + ); 95 95 } 96 96 97 97 function FieldContent({ className, ...props }: React.ComponentProps<"div">) { ··· 100 100 data-slot="field-content" 101 101 className={cn( 102 102 "group/field-content flex flex-1 flex-col gap-1.5 leading-snug", 103 - className 103 + className, 104 104 )} 105 105 {...props} 106 106 /> 107 - ) 107 + ); 108 108 } 109 109 110 110 function FieldLabel({ ··· 118 118 "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50", 119 119 "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4", 120 120 "has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10", 121 - className 121 + className, 122 122 )} 123 123 {...props} 124 124 /> 125 - ) 125 + ); 126 126 } 127 127 128 128 function FieldTitle({ className, ...props }: React.ComponentProps<"div">) { ··· 131 131 data-slot="field-label" 132 132 className={cn( 133 133 "flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50", 134 - className 134 + className, 135 135 )} 136 136 {...props} 137 137 /> 138 - ) 138 + ); 139 139 } 140 140 141 141 function FieldDescription({ className, ...props }: React.ComponentProps<"p">) { ··· 146 146 "text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance", 147 147 "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5", 148 148 "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", 149 - className 149 + className, 150 150 )} 151 151 {...props} 152 152 /> 153 - ) 153 + ); 154 154 } 155 155 156 156 function FieldSeparator({ ··· 158 158 className, 159 159 ...props 160 160 }: React.ComponentProps<"div"> & { 161 - children?: React.ReactNode 161 + children?: React.ReactNode; 162 162 }) { 163 163 return ( 164 164 <div ··· 166 166 data-content={!!children} 167 167 className={cn( 168 168 "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2", 169 - className 169 + className, 170 170 )} 171 171 {...props} 172 172 > ··· 180 180 </span> 181 181 )} 182 182 </div> 183 - ) 183 + ); 184 184 } 185 185 186 186 function FieldError({ ··· 189 189 errors, 190 190 ...props 191 191 }: React.ComponentProps<"div"> & { 192 - errors?: Array<{ message?: string } | undefined> 192 + errors?: Array<{ message?: string } | undefined>; 193 193 }) { 194 194 const content = useMemo(() => { 195 195 if (children) { 196 - return children 196 + return children; 197 197 } 198 198 199 199 if (!errors?.length) { 200 - return null 200 + return null; 201 201 } 202 202 203 203 const uniqueErrors = [ 204 204 ...new Map(errors.map((error) => [error?.message, error])).values(), 205 - ] 205 + ]; 206 206 207 - if (uniqueErrors?.length == 1) { 208 - return uniqueErrors[0]?.message 207 + if (uniqueErrors?.length === 1) { 208 + return uniqueErrors[0]?.message; 209 209 } 210 210 211 211 return ( 212 212 <ul className="ml-4 flex list-disc flex-col gap-1"> 213 213 {uniqueErrors.map( 214 214 (error, index) => 215 - error?.message && <li key={index}>{error.message}</li> 215 + error?.message && <li key={index}>{error.message}</li>, 216 216 )} 217 217 </ul> 218 - ) 219 - }, [children, errors]) 218 + ); 219 + }, [children, errors]); 220 220 221 221 if (!content) { 222 - return null 222 + return null; 223 223 } 224 224 225 225 return ( ··· 231 231 > 232 232 {content} 233 233 </div> 234 - ) 234 + ); 235 235 } 236 236 237 237 export { ··· 245 245 FieldSet, 246 246 FieldContent, 247 247 FieldTitle, 248 - } 248 + };
+5 -5
src/components/ui/input.tsx
··· 1 - import * as React from "react" 1 + import * as React from "react"; 2 2 3 - import { cn } from "@/lib/utils" 3 + import { cn } from "@/lib/utils"; 4 4 5 5 function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 6 return ( ··· 11 11 "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 12 12 "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", 13 13 "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 14 - className 14 + className, 15 15 )} 16 16 {...props} 17 17 /> 18 - ) 18 + ); 19 19 } 20 20 21 - export { Input } 21 + export { Input };
+7 -7
src/components/ui/label.tsx
··· 1 - "use client" 1 + "use client"; 2 2 3 - import * as React from "react" 4 - import { Label as LabelPrimitive } from "radix-ui" 3 + import * as React from "react"; 4 + import { Label as LabelPrimitive } from "radix-ui"; 5 5 6 - import { cn } from "@/lib/utils" 6 + import { cn } from "@/lib/utils"; 7 7 8 8 function Label({ 9 9 className, ··· 14 14 data-slot="label" 15 15 className={cn( 16 16 "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", 17 - className 17 + className, 18 18 )} 19 19 {...props} 20 20 /> 21 - ) 21 + ); 22 22 } 23 23 24 - export { Label } 24 + export { Label };
+7 -7
src/components/ui/separator.tsx
··· 1 - "use client" 1 + "use client"; 2 2 3 - import * as React from "react" 4 - import { Separator as SeparatorPrimitive } from "radix-ui" 3 + import * as React from "react"; 4 + import { Separator as SeparatorPrimitive } from "radix-ui"; 5 5 6 - import { cn } from "@/lib/utils" 6 + import { cn } from "@/lib/utils"; 7 7 8 8 function Separator({ 9 9 className, ··· 18 18 orientation={orientation} 19 19 className={cn( 20 20 "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", 21 - className 21 + className, 22 22 )} 23 23 {...props} 24 24 /> 25 - ) 25 + ); 26 26 } 27 27 28 - export { Separator } 28 + export { Separator };
+3 -3
src/lib/utils.ts
··· 1 - import { clsx, type ClassValue } from "clsx" 2 - import { twMerge } from "tailwind-merge" 1 + import { clsx, type ClassValue } from "clsx"; 2 + import { twMerge } from "tailwind-merge"; 3 3 4 4 export function cn(...inputs: ClassValue[]) { 5 - return twMerge(clsx(inputs)) 5 + return twMerge(clsx(inputs)); 6 6 }
+2
tsconfig.json
··· 5 5 "allowJs": true, 6 6 "skipLibCheck": true, 7 7 "strict": true, 8 + "noUncheckedIndexedAccess": true, 9 + "noImplicitReturns": true, 8 10 "noEmit": true, 9 11 "esModuleInterop": true, 10 12 "module": "esnext",