├── .nvmrc ├── apps ├── docs │ ├── static │ │ ├── .nojekyll │ │ └── img │ │ │ ├── favicon.ico │ │ │ └── docusaurus.png │ ├── babel.config.js │ ├── docs │ │ ├── 04-web │ │ │ ├── _category_.json │ │ │ ├── ui.mdx │ │ │ └── webapp.mdx │ │ ├── 03-backend │ │ │ ├── _category_.json │ │ │ └── api.mdx │ │ └── 05-next-steps.mdx │ ├── tsconfig.json │ ├── sidebars.ts │ ├── src │ │ ├── css │ │ │ └── custom.css │ │ └── pages │ │ │ └── index.md │ └── package.json ├── web │ ├── postcss.config.cjs │ ├── public │ │ ├── favicon.ico │ │ ├── fonts │ │ │ └── hanken-grotesk │ │ │ │ ├── HankenGrotesk-Black.ttf │ │ │ │ ├── HankenGrotesk-Bold.ttf │ │ │ │ ├── HankenGrotesk-Light.ttf │ │ │ │ ├── HankenGrotesk-Thin.ttf │ │ │ │ ├── HankenGrotesk-Italic.ttf │ │ │ │ ├── HankenGrotesk-Medium.ttf │ │ │ │ ├── HankenGrotesk-Regular.ttf │ │ │ │ ├── HankenGrotesk-SemiBold.ttf │ │ │ │ ├── HankenGrotesk-BoldItalic.ttf │ │ │ │ ├── HankenGrotesk-ExtraBold.ttf │ │ │ │ ├── HankenGrotesk-ExtraLight.ttf │ │ │ │ ├── HankenGrotesk-ThinItalic.ttf │ │ │ │ ├── HankenGrotesk-BlackItalic.ttf │ │ │ │ ├── HankenGrotesk-LightItalic.ttf │ │ │ │ ├── HankenGrotesk-MediumItalic.ttf │ │ │ │ ├── HankenGrotesk-ExtraBoldItalic.ttf │ │ │ │ ├── HankenGrotesk-SemiBoldItalic.ttf │ │ │ │ └── HankenGrotesk-ExtraLightItalic.ttf │ │ ├── svg │ │ │ ├── checkmark.svg │ │ │ ├── toastX.svg │ │ │ ├── perkCheck.svg │ │ │ ├── toastCheck.svg │ │ │ ├── perkCross.svg │ │ │ ├── bookmark.svg │ │ │ ├── filledBookmark.svg │ │ │ ├── hoverBookmark.svg │ │ │ ├── burgerMenu.svg │ │ │ ├── compareAdd.svg │ │ │ ├── compareRole.svg │ │ │ ├── reviewReport.svg │ │ │ ├── star.svg │ │ │ ├── apartment.svg │ │ │ ├── magnifyingGlass.svg │ │ │ ├── work.svg │ │ │ ├── xSymbol.svg │ │ │ ├── verified.svg │ │ │ ├── defaultProfile.svg │ │ │ └── logoOutline.svg │ │ └── logo.svg │ ├── test │ │ └── app.test.ts │ ├── src │ │ ├── app │ │ │ ├── _components │ │ │ │ ├── form │ │ │ │ │ ├── form-card.tsx │ │ │ │ │ ├── form-section.tsx │ │ │ │ │ └── sections │ │ │ │ │ │ └── index.ts │ │ │ │ ├── auth │ │ │ │ │ ├── actions.ts │ │ │ │ │ ├── login-button-client.tsx │ │ │ │ │ ├── logout-button.tsx │ │ │ │ │ └── login-button.tsx │ │ │ │ ├── themed │ │ │ │ │ └── onboarding │ │ │ │ │ │ ├── input.tsx │ │ │ │ │ │ └── form.tsx │ │ │ │ ├── cooper-logo.tsx │ │ │ │ ├── body-logo.tsx │ │ │ │ ├── back-button.tsx │ │ │ │ ├── onboarding │ │ │ │ │ ├── post-onboarding │ │ │ │ │ │ ├── coop-prompt.tsx │ │ │ │ │ │ ├── browse-around-prompt.tsx │ │ │ │ │ │ └── welcome-dialog.tsx │ │ │ │ │ ├── onboarding-wrapper.tsx │ │ │ │ │ └── dialog.tsx │ │ │ │ ├── loading-results.tsx │ │ │ │ ├── companies │ │ │ │ │ ├── company-about.tsx │ │ │ │ │ ├── all-company-roles.tsx │ │ │ │ │ └── company-reviews.tsx │ │ │ │ ├── reviews │ │ │ │ │ ├── info-card.tsx │ │ │ │ │ ├── review-search-bar.tsx │ │ │ │ │ ├── new-role-card.tsx │ │ │ │ │ ├── bar-graph.tsx │ │ │ │ │ ├── collapsable-info.tsx │ │ │ │ │ └── round-bar-graph.tsx │ │ │ │ ├── header │ │ │ │ │ ├── header-layout.tsx │ │ │ │ │ ├── mobile-header-button.tsx │ │ │ │ │ └── header-layout-client.tsx │ │ │ │ ├── screen-size-indicator.tsx │ │ │ │ ├── shared │ │ │ │ │ └── favorite-button.tsx │ │ │ │ ├── search │ │ │ │ │ └── simple-search-bar.tsx │ │ │ │ ├── no-results.tsx │ │ │ │ ├── profile │ │ │ │ │ ├── profile-button-client.tsx │ │ │ │ │ ├── profile-button.tsx │ │ │ │ │ ├── profile-tabs.tsx │ │ │ │ │ └── favorite-company-search.tsx │ │ │ │ └── footer.tsx │ │ │ ├── (pages) │ │ │ │ ├── (dashboard) │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── redirection │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── (roles) │ │ │ │ │ │ └── role │ │ │ │ │ │ └── page.tsx │ │ │ │ └── (protected) │ │ │ │ │ └── layout.tsx │ │ │ ├── styles │ │ │ │ └── font.ts │ │ │ ├── not-found.tsx │ │ │ ├── api │ │ │ │ └── trpc │ │ │ │ │ └── [trpc] │ │ │ │ │ └── route.ts │ │ │ ├── robots.txt │ │ │ └── layout.tsx │ │ ├── middleware.ts │ │ ├── utils │ │ │ ├── reviewCountByStars.ts │ │ │ ├── dateHelpers.ts │ │ │ └── reviewsAggregationHelpers.ts │ │ ├── trpc │ │ │ ├── query-client.ts │ │ │ └── server.ts │ │ └── env.ts │ ├── eslint.config.js │ ├── tsconfig.json │ ├── next.config.js │ ├── README.md │ └── package.json └── auth-proxy │ ├── tsconfig.json │ ├── .output │ ├── server │ │ ├── index.mjs.map │ │ ├── index.mjs │ │ ├── package.json │ │ └── chunks │ │ │ └── routes │ │ │ └── r │ │ │ ├── _...auth_.mjs.map │ │ │ └── _...auth_.mjs │ └── nitro.json │ ├── .env.example │ ├── .nitro │ └── types │ │ ├── nitro.d.ts │ │ ├── nitro-config.d.ts │ │ ├── nitro-routes.d.ts │ │ └── tsconfig.json │ ├── eslint.config.js │ ├── turbo.json │ ├── routes │ └── r │ │ └── [...auth].ts │ ├── package.json │ └── README.md ├── .npmrc ├── tooling ├── github │ ├── package.json │ └── setup │ │ └── action.yml ├── typescript │ ├── package.json │ ├── internal-package.json │ └── base.json ├── tailwind │ ├── eslint.config.js │ ├── native.ts │ ├── tsconfig.json │ ├── package.json │ └── base.ts ├── eslint │ ├── tsconfig.json │ ├── nextjs.js │ ├── react.js │ ├── package.json │ └── types.d.ts └── prettier │ ├── tsconfig.json │ ├── package.json │ └── index.js ├── packages ├── db │ ├── drizzle │ │ ├── 0007_simple_korg.sql │ │ ├── 0001_petite_white_tiger.sql │ │ ├── 0004_cooing_the_leader.sql │ │ ├── 0006_nappy_star_brand.sql │ │ ├── 0005_productive_hulk.sql │ │ ├── meta │ │ │ └── _journal.json │ │ └── 0003_silky_mister_fear.sql │ ├── src │ │ ├── index.ts │ │ ├── client.ts │ │ ├── utils │ │ │ └── enums.ts │ │ └── schema │ │ │ ├── sessions.ts │ │ │ ├── users.ts │ │ │ ├── locations.ts │ │ │ ├── profilesToRoles.ts │ │ │ ├── profliesToReviews.ts │ │ │ ├── profilesToCompanies.ts │ │ │ ├── companiesToLocations.ts │ │ │ ├── accounts.ts │ │ │ ├── roleRequest.ts │ │ │ ├── companies.ts │ │ │ ├── companyRequest.ts │ │ │ ├── roles.ts │ │ │ ├── misc.ts │ │ │ └── profiles.ts │ ├── eslint.config.js │ ├── tsconfig.json │ ├── drizzle.config.ts │ └── package.json ├── ui │ ├── src │ │ ├── icons.tsx │ │ ├── index.ts │ │ ├── chip.tsx │ │ ├── label.tsx │ │ ├── toaster.tsx │ │ ├── textarea.tsx │ │ ├── input.tsx │ │ ├── hooks │ │ │ └── use-custom-toast.ts │ │ ├── checkbox.tsx │ │ ├── popover.tsx │ │ ├── logo.tsx │ │ ├── radio-group.tsx │ │ ├── error-toast.tsx │ │ └── success-toast.tsx │ ├── eslint.config.js │ ├── tsconfig.json │ ├── tailwind.config.ts │ ├── components.json │ └── package.json ├── api │ ├── eslint.config.js │ ├── tests │ │ ├── mocks │ │ │ ├── role.ts │ │ │ ├── review.ts │ │ │ └── company.ts │ │ └── role.test.ts │ ├── tsconfig.json │ ├── src │ │ ├── utils │ │ │ ├── fuzzyHelper.ts │ │ │ └── slugHelpers.ts │ │ ├── router │ │ │ ├── index.ts │ │ │ ├── auth.ts │ │ │ ├── companytoLocation.ts │ │ │ └── location.ts │ │ ├── root.ts │ │ └── index.ts │ └── package.json ├── auth │ ├── tsconfig.json │ ├── eslint.config.js │ ├── src │ │ ├── index.ts │ │ └── index.rsc.ts │ ├── env.ts │ └── package.json ├── validators │ ├── eslint.config.js │ ├── tsconfig.json │ ├── src │ │ └── index.ts │ └── package.json └── scraper │ ├── tsconfig.json │ ├── scraped │ └── seed data │ │ ├── add_id.py │ │ └── companies_to_csv.py │ └── package.json ├── vitest.workspace.ts ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── turbo └── generators │ └── templates │ ├── eslint.config.js.hbs │ ├── tsconfig.json.hbs │ └── package.json.hbs ├── pnpm-workspace.yaml ├── compose.yml ├── .github ├── workflows │ ├── preview-migration.yml │ ├── migration.yml │ ├── assign-reviewers.yml │ └── ci.yml └── pull_request_template.md ├── .gitignore ├── .env.example ├── package.json └── turbo.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.12 -------------------------------------------------------------------------------- /apps/docs/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted 2 | -------------------------------------------------------------------------------- /tooling/github/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cooper/github" 3 | } 4 | -------------------------------------------------------------------------------- /packages/db/drizzle/0007_simple_korg.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "role" ALTER COLUMN "jobType" SET NOT NULL; -------------------------------------------------------------------------------- /apps/web/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /apps/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/favicon.ico -------------------------------------------------------------------------------- /packages/db/drizzle/0001_petite_white_tiger.sql: -------------------------------------------------------------------------------- 1 | -- ALTER TABLE "role" ADD COLUMN "createdBy" varchar NOT NULL; -------------------------------------------------------------------------------- /packages/db/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "drizzle-orm/sql"; 2 | export { alias } from "drizzle-orm/pg-core"; 3 | -------------------------------------------------------------------------------- /packages/ui/src/icons.tsx: -------------------------------------------------------------------------------- 1 | import { CheckIcon } from "@radix-ui/react-icons"; 2 | 3 | export { CheckIcon }; 4 | -------------------------------------------------------------------------------- /apps/auth-proxy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cooper/tsconfig/base.json", 3 | "include": ["routes"] 4 | } 5 | -------------------------------------------------------------------------------- /apps/docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /packages/db/drizzle/0004_cooing_the_leader.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "role" 2 | DROP CONSTRAINT IF EXISTS "role_slug_unique"; -------------------------------------------------------------------------------- /apps/docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /packages/db/drizzle/0006_nappy_star_brand.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "review" RENAME COLUMN "freeTransport" TO "travelBenefits"; 2 | -------------------------------------------------------------------------------- /apps/docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve("@docusaurus/core/lib/babel/preset")], 3 | }; 4 | -------------------------------------------------------------------------------- /apps/auth-proxy/.output/server/index.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.mjs","sources":[],"sourcesContent":null,"names":[],"mappings":";;;;;"} -------------------------------------------------------------------------------- /apps/web/test/app.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | 3 | test("should pass", () => { 4 | expect(true).toBe(true); 5 | }); 6 | -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from "vitest/config"; 2 | 3 | export default defineWorkspace(["packages/*", "apps/*", "tooling/*"]); 4 | -------------------------------------------------------------------------------- /apps/auth-proxy/.env.example: -------------------------------------------------------------------------------- 1 | AUTH_SECRET="" 2 | AUTH_GOOGLE_ID="" 3 | AUTH_GOOGLE_SECRET="" 4 | AUTH_REDIRECT_PROXY_URL="" 5 | 6 | NITRO_PRESET="vercel_edge" -------------------------------------------------------------------------------- /tooling/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cooper/tsconfig", 3 | "private": true, 4 | "version": "0.1.0", 5 | "files": [ 6 | "*.json" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Black.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Bold.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Light.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Thin.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Italic.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Medium.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-Regular.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-SemiBold.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-BoldItalic.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-ExtraBold.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-ExtraLight.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-ThinItalic.ttf -------------------------------------------------------------------------------- /apps/auth-proxy/.nitro/types/nitro.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-BlackItalic.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-LightItalic.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-MediumItalic.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/hanken-grotesk/HankenGrotesk-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandboxnu/cooper/main/apps/web/public/fonts/hanken-grotesk/HankenGrotesk-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "yoavbls.pretty-ts-errors", 6 | "bradlc.vscode-tailwindcss" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /turbo/generators/templates/eslint.config.js.hbs: -------------------------------------------------------------------------------- 1 | import baseConfig from "@cooper/eslint-config/base"; /** @type 2 | {import('typescript-eslint').Config} */ export default [ { ignores: [], }, 3 | ...baseConfig, ]; -------------------------------------------------------------------------------- /apps/docs/docs/04-web/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Web", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Documentation for the Cooper web app." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/db/src/client.ts: -------------------------------------------------------------------------------- 1 | import { sql } from "@vercel/postgres"; 2 | import { drizzle } from "drizzle-orm/vercel-postgres"; 3 | 4 | import * as schema from "./schema"; 5 | 6 | export const db = drizzle(sql, { schema }); 7 | -------------------------------------------------------------------------------- /apps/docs/docs/03-backend/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Backend", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Documentation for the backend of the Cooper app." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tooling/tailwind/eslint.config.js: -------------------------------------------------------------------------------- 1 | // FIXME: This kinda stinks... 2 | /// 3 | 4 | import baseConfig from "@cooper/eslint-config/base"; 5 | 6 | export default [...baseConfig]; 7 | -------------------------------------------------------------------------------- /tooling/tailwind/native.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | import base from "./base"; 4 | 5 | export default { 6 | content: base.content, 7 | presets: [base], 8 | theme: {}, 9 | } satisfies Config; 10 | -------------------------------------------------------------------------------- /turbo/generators/templates/tsconfig.json.hbs: -------------------------------------------------------------------------------- 1 | { "extends": "@cooper/tsconfig/base.json", "compilerOptions": { 2 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" }, "include": ["*.ts", 3 | "src"], "exclude": ["node_modules"] } -------------------------------------------------------------------------------- /tooling/eslint/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cooper/tsconfig/base.json", 3 | "compilerOptions": { 4 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" 5 | }, 6 | "include": ["."], 7 | "exclude": ["node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/api/eslint.config.js: -------------------------------------------------------------------------------- 1 | import baseConfig from "@cooper/eslint-config/base"; 2 | 3 | /** @type {import('typescript-eslint').Config} */ 4 | export default [ 5 | { 6 | ignores: ["dist/**"], 7 | }, 8 | ...baseConfig, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/api/tests/mocks/role.ts: -------------------------------------------------------------------------------- 1 | export const data = [ 2 | { title: "Software Engineer", description: "you write code" }, 3 | { title: "Journalist", description: "you write words" }, 4 | { title: "Artist", description: "you draw" }, 5 | ]; 6 | -------------------------------------------------------------------------------- /packages/api/tests/role.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "vitest"; 2 | 3 | describe("Company Router", () => { 4 | test("github action bandaage", () => { 5 | expect(true).toBe(true); 6 | }); 7 | // Add your tests here 8 | }); 9 | -------------------------------------------------------------------------------- /tooling/prettier/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cooper/tsconfig/base.json", 3 | "compilerOptions": { 4 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" 5 | }, 6 | "include": ["."], 7 | "exclude": ["node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /tooling/tailwind/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cooper/tsconfig/base.json", 3 | "compilerOptions": { 4 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" 5 | }, 6 | "include": ["."], 7 | "exclude": ["node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cooper/tsconfig/base.json", 3 | "compilerOptions": { 4 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" 5 | }, 6 | "include": ["src", "*.ts"], 7 | "exclude": ["node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/validators/eslint.config.js: -------------------------------------------------------------------------------- 1 | import baseConfig from "@cooper/eslint-config/base"; 2 | 3 | /** @type {import('typescript-eslint').Config} */ 4 | export default [ 5 | { 6 | ignores: ["dist/**"], 7 | }, 8 | ...baseConfig, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/api/tests/mocks/review.ts: -------------------------------------------------------------------------------- 1 | export const data = [ 2 | { id: "1", workTerm: "SPRING", workEnvironment: "REMOTE" }, 3 | { id: "2", workTerm: "FALL", workEnvironment: "INPERSON" }, 4 | { id: "3", workTerm: "SUMMER", workEnvironment: "HYBRID" }, 5 | ]; 6 | -------------------------------------------------------------------------------- /apps/auth-proxy/eslint.config.js: -------------------------------------------------------------------------------- 1 | import baseConfig from "@cooper/eslint-config/base"; 2 | 3 | /** @type {import('typescript-eslint').Config} */ 4 | export default [ 5 | { 6 | ignores: [".nitro/**", ".output/**"], 7 | }, 8 | ...baseConfig, 9 | ]; 10 | -------------------------------------------------------------------------------- /apps/web/public/svg/checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/scraper/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "CommonJS", 5 | "lib": ["esnext", "dom"], 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/web/public/svg/toastX.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/auth/eslint.config.js: -------------------------------------------------------------------------------- 1 | import baseConfig, { restrictEnvAccess } from "@cooper/eslint-config/base"; 2 | 3 | /** @type {import('typescript-eslint').Config} */ 4 | export default [ 5 | { 6 | ignores: [], 7 | }, 8 | ...baseConfig, 9 | ...restrictEnvAccess, 10 | ]; 11 | -------------------------------------------------------------------------------- /packages/db/eslint.config.js: -------------------------------------------------------------------------------- 1 | import baseConfig, { restrictEnvAccess } from "@cooper/eslint-config/base"; 2 | 3 | /** @type {import('typescript-eslint').Config} */ 4 | export default [ 5 | { 6 | ignores: ["dist/**"], 7 | }, 8 | ...baseConfig, 9 | ...restrictEnvAccess, 10 | ]; 11 | -------------------------------------------------------------------------------- /apps/auth-proxy/.nitro/types/nitro-config.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by nitro 2 | 3 | // App Config 4 | import type { Defu } from "defu"; 5 | 6 | type UserAppConfig = Defu<{}, []>; 7 | 8 | declare module "nitropack" { 9 | interface AppConfig extends UserAppConfig {} 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /packages/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cooper/tsconfig/internal-package.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" 6 | }, 7 | "include": ["src", "tests"], 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/form/form-card.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * FormCard component provides a structured container to organize form elements. 3 | */ 4 | export function FormCard({ children }: { children: React.ReactNode }) { 5 | return
{children}
; 6 | } 7 | -------------------------------------------------------------------------------- /packages/validators/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cooper/tsconfig/internal-package.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" 6 | }, 7 | "include": ["*.ts", "src"], 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/ui/eslint.config.js: -------------------------------------------------------------------------------- 1 | import baseConfig from "@cooper/eslint-config/base"; 2 | import reactConfig from "@cooper/eslint-config/react"; 3 | 4 | /** @type {import('typescript-eslint').Config} */ 5 | export default [ 6 | { 7 | ignores: [], 8 | }, 9 | ...baseConfig, 10 | ...reactConfig, 11 | ]; 12 | -------------------------------------------------------------------------------- /packages/db/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cooper/tsconfig/internal-package.json", 3 | "compilerOptions": { 4 | "strictNullChecks": true, 5 | "outDir": "dist", 6 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" 7 | }, 8 | "include": ["src"], 9 | "exclude": ["node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/validators/src/index.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const unused = z.string().describe( 4 | `This lib is currently not used as we use drizzle-zod for simple schemas 5 | But as the application grows and we need other validators to share 6 | with back and frontend, we can put them in here 7 | `, 8 | ); 9 | -------------------------------------------------------------------------------- /apps/auth-proxy/.output/nitro.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2025-11-14T23:53:58.107Z", 3 | "preset": "node-server", 4 | "framework": { 5 | "name": "nitro", 6 | "version": "2.9.7" 7 | }, 8 | "versions": { 9 | "nitro": "2.9.7" 10 | }, 11 | "commands": { 12 | "preview": "node ./server/index.mjs" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/auth-proxy/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "outputs": [".nitro/**", ".output/**", ".vercel/**"] 8 | }, 9 | "dev": { 10 | "persistent": true 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/auth/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { signIn, signOut } from "@cooper/auth"; 4 | 5 | export async function handleGoogleSignIn() { 6 | await signIn("google", { redirectTo: "/" }); 7 | } 8 | 9 | export async function handleSignOut() { 10 | await signOut({ redirectTo: "/" }); 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/public/svg/perkCheck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Next.js", 6 | "type": "node-terminal", 7 | "request": "launch", 8 | "command": "pnpm dev", 9 | "cwd": "${workspaceFolder}/apps/web/", 10 | "skipFiles": ["/**"] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/form/form-section.tsx: -------------------------------------------------------------------------------- 1 | import { FormCard } from "~/app/_components/form/form-card"; 2 | 3 | /** 4 | * FormSection component creates a section within a FormCard with a specified title. 5 | */ 6 | export function FormSection({ children }: { children: React.ReactNode }) { 7 | return {children}; 8 | } 9 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cooper/tsconfig/internal-package.json", 3 | "compilerOptions": { 4 | "lib": ["dom", "dom.iterable", "ES2022"], 5 | "jsx": "preserve", 6 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" 7 | }, 8 | "include": ["*.ts", "src"], 9 | "exclude": ["node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/web/public/svg/toastCheck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/auth-proxy/.output/server/index.mjs: -------------------------------------------------------------------------------- 1 | import process from "node:process"; 2 | globalThis._importMeta_ = { url: import.meta.url, env: process.env }; 3 | import "node:http"; 4 | import "node:https"; 5 | export { n as default } from "./chunks/runtime.mjs"; 6 | import "node:fs"; 7 | import "node:path"; 8 | import "node:url"; 9 | //# sourceMappingURL=index.mjs.map 10 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - apps/* 3 | - packages/* 4 | - tooling/* 5 | 6 | catalog: 7 | eslint: ^9.6.0 8 | prettier: ^3.3.2 9 | typescript: ^5.5.3 10 | zod: ^3.23.8 11 | vitest: ^2.1.1 12 | 13 | catalogs: 14 | react18: 15 | react: 18.3.1 16 | react-dom: 18.3.1 17 | "@types/react": ^18.3.3 18 | "@types/react-dom": ^18.3.0 19 | -------------------------------------------------------------------------------- /tooling/typescript/internal-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./base.json", 4 | "compilerOptions": { 5 | /** Emit types for internal packages to speed up editor performance. */ 6 | "declaration": true, 7 | "declarationMap": true, 8 | "noEmit": false, 9 | "emitDeclarationOnly": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/public/svg/perkCross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/themed/onboarding/input.tsx: -------------------------------------------------------------------------------- 1 | import { Input as InputPrimitive } from "@cooper/ui/input"; 2 | 3 | export function Input(props: React.ComponentProps) { 4 | return ( 5 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /packages/db/src/utils/enums.ts: -------------------------------------------------------------------------------- 1 | // TODO: Does not work as expected -- sets the varchar field length to the number of variants (not the intended behavior) 2 | export function enumToPgEnum( 3 | enumeration: Record, 4 | ): [string, ...string[]] { 5 | return Object.values(enumeration).map((value: string) => `${value}`) as [ 6 | string, 7 | ...string[], 8 | ]; 9 | } 10 | -------------------------------------------------------------------------------- /packages/api/tests/mocks/company.ts: -------------------------------------------------------------------------------- 1 | export const data = [ 2 | { 3 | name: "Draft Kings", 4 | description: "sports gambling company", 5 | industry: "Sports", 6 | }, 7 | { name: "Klaviyo", description: "fire company", industry: "Tech" }, 8 | { 9 | name: "Hubspot", 10 | description: "a company where a lot of our alum work", 11 | industry: "Business", 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /packages/auth/src/index.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | 3 | import { authConfig } from "./config"; 4 | 5 | export type { Session } from "next-auth"; 6 | 7 | const { handlers, auth, signIn, signOut } = NextAuth(authConfig); 8 | 9 | export { handlers, auth, signIn, signOut }; 10 | 11 | export { 12 | invalidateSessionToken, 13 | validateToken, 14 | isSecureContext, 15 | } from "./config"; 16 | -------------------------------------------------------------------------------- /packages/db/drizzle/0005_productive_hulk.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "profile" ALTER COLUMN "updatedAt" SET DEFAULT now();--> statement-breakpoint 2 | ALTER TABLE "profile" ALTER COLUMN "updatedAt" SET NOT NULL;--> statement-breakpoint 3 | ALTER TABLE "review" ADD COLUMN IF NOT EXISTS "snackBar" boolean DEFAULT false NOT NULL;--> statement-breakpoint 4 | ALTER TABLE "role" ADD COLUMN IF NOT EXISTS "jobType" varchar DEFAULT 'CO-OP'; -------------------------------------------------------------------------------- /packages/ui/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is not used for any compilation purpose, it is only used 3 | * for Tailwind Intellisense & Autocompletion in the source files 4 | */ 5 | import type { Config } from "tailwindcss"; 6 | 7 | import baseConfig from "@cooper/tailwind-config/web"; 8 | 9 | export default { 10 | content: ["./src/**/*.tsx"], 11 | presets: [baseConfig], 12 | } satisfies Config; 13 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/cooper-logo.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | interface CooperLogoProps { 4 | width?: number; 5 | } 6 | 7 | export default function CooperLogo({ width }: CooperLogoProps) { 8 | return ( 9 | Logo Picture 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/ui/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "./tailwind.config.ts", 8 | "css": "unused.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "utils": "@cooper/ui", 14 | "components": "src/", 15 | "ui": "src/" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tooling/github/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup and install" 2 | description: "Common setup steps for Actions" 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - uses: pnpm/action-setup@v4 8 | - uses: actions/setup-node@v4 9 | with: 10 | node-version: 20 11 | cache: "pnpm" 12 | 13 | - shell: bash 14 | run: pnpm add -g turbo 15 | 16 | - shell: bash 17 | run: pnpm install 18 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/body-logo.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | interface CooperLogoProps { 4 | width?: number; 5 | } 6 | 7 | export default function BodyLogo({ width }: CooperLogoProps) { 8 | return ( 9 | Logo Picture 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | # FIXME: This currently does not work with the Drizzle migrations 5 | postgres: 6 | image: postgres 7 | restart: always 8 | environment: 9 | - POSTGRES_USER=admin 10 | - POSTGRES_PASSWORD=admin 11 | - POSTGRES_DB=cooper 12 | volumes: 13 | - postgres:/var/lib/postgresql/data 14 | ports: 15 | - "5432:5432" 16 | volumes: 17 | postgres: 18 | -------------------------------------------------------------------------------- /apps/web/public/svg/bookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/auth-proxy/.output/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cooper/auth-proxy-prod", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "dependencies": { 7 | "@auth/core": "0.32.0", 8 | "@panva/hkdf": "1.2.1", 9 | "cookie": "0.6.0", 10 | "jose": "5.6.3", 11 | "oauth4webapi": "2.11.1", 12 | "preact": "10.11.3", 13 | "preact-render-to-string": "5.2.3", 14 | "std-env": "3.7.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/eslint.config.js: -------------------------------------------------------------------------------- 1 | import baseConfig, { restrictEnvAccess } from "@cooper/eslint-config/base"; 2 | import nextjsConfig from "@cooper/eslint-config/nextjs"; 3 | import reactConfig from "@cooper/eslint-config/react"; 4 | 5 | /** @type {import('typescript-eslint').Config} */ 6 | export default [ 7 | { 8 | ignores: [".next/**"], 9 | }, 10 | ...baseConfig, 11 | ...reactConfig, 12 | ...nextjsConfig, 13 | ...restrictEnvAccess, 14 | ]; 15 | -------------------------------------------------------------------------------- /apps/web/src/middleware.ts: -------------------------------------------------------------------------------- 1 | export { auth as middleware } from "@cooper/auth"; 2 | 3 | // Or like this if you need to do something here. 4 | // export default auth((req) => { 5 | // console.log(req.auth) // { session: { user: { ... } } } 6 | // }) 7 | 8 | // Read more: https://web.org/docs/app/building-your-application/routing/middleware#matcher 9 | export const config = { 10 | matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], 11 | }; 12 | -------------------------------------------------------------------------------- /packages/api/src/utils/fuzzyHelper.ts: -------------------------------------------------------------------------------- 1 | import Fuse from "fuse.js"; 2 | 3 | export function performFuseSearch( 4 | elem: T[], 5 | options: string[], 6 | searchQuery: string | undefined, 7 | ): T[] { 8 | if (!searchQuery) { 9 | return elem; 10 | } 11 | 12 | const fuseOptions = { 13 | keys: options, 14 | }; 15 | 16 | const fuse = new Fuse(elem, fuseOptions); 17 | return fuse.search(searchQuery).map((result) => result.item); 18 | } 19 | -------------------------------------------------------------------------------- /apps/web/public/svg/filledBookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/web/public/svg/hoverBookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/ui/src/index.ts: -------------------------------------------------------------------------------- 1 | import { cx } from "class-variance-authority"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | const cn = (...inputs: Parameters) => twMerge(cx(inputs)); 5 | 6 | export { cn }; 7 | 8 | export { CustomToaster } from "./custom-toaster"; 9 | export { useCustomToast } from "./hooks/use-custom-toast"; 10 | export { SuccessToast } from "./success-toast"; 11 | export { ErrorToast } from "./error-toast"; 12 | export { Pagination } from "./pagination"; 13 | -------------------------------------------------------------------------------- /apps/auth-proxy/.nitro/types/nitro-routes.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by nitro 2 | import type { Serialize, Simplify } from "nitropack"; 3 | declare module "nitropack" { 4 | type Awaited = T extends PromiseLike ? Awaited : T; 5 | interface InternalApi { 6 | "/r/**:auth": { 7 | default: Simplify< 8 | Serialize< 9 | Awaited> 10 | > 11 | >; 12 | }; 13 | } 14 | } 15 | export {}; 16 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cooper/tsconfig/base.json", 3 | "compilerOptions": { 4 | "lib": ["es2022", "dom", "dom.iterable"], 5 | "jsx": "preserve", 6 | "baseUrl": ".", 7 | "paths": { 8 | "~/*": ["./src/*"] 9 | }, 10 | "plugins": [{ "name": "next" }], 11 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", 12 | "module": "esnext" 13 | }, 14 | "include": [".", ".next/types/**/*.ts"], 15 | "exclude": ["node_modules"] 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/public/svg/burgerMenu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/db/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "drizzle-kit"; 2 | 3 | if (!process.env.POSTGRES_URL) { 4 | throw new Error("Missing POSTGRES_URL"); 5 | } 6 | 7 | const nonPoolingUrl = process.env.POSTGRES_URL.replace(":6543", ":5432"); 8 | 9 | export default { 10 | schema: "./src/schema", 11 | out: "./drizzle", 12 | dialect: "postgresql", 13 | dbCredentials: { 14 | url: nonPoolingUrl, 15 | ssl: { 16 | rejectUnauthorized: false, 17 | }, 18 | }, 19 | } satisfies Config; 20 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/back-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter } from "next/navigation"; 4 | 5 | import { Button } from "@cooper/ui/button"; 6 | 7 | export default function BackButton() { 8 | const router = useRouter(); 9 | return ( 10 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/src/utils/reviewCountByStars.ts: -------------------------------------------------------------------------------- 1 | import type { ReviewType } from "@cooper/db/schema"; 2 | 3 | export function calculateRatings(reviews: ReviewType[] = []) { 4 | const totalReviews = reviews.length; 5 | 6 | return [5, 4, 3, 2, 1].map((star) => { 7 | const count = reviews.filter( 8 | (r) => r.overallRating.toFixed(0) === star.toString(), 9 | ).length; 10 | const percentage = 11 | totalReviews > 0 ? Math.round((count / totalReviews) * 100) : 0; 12 | return { stars: star, percentage }; 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /tooling/eslint/nextjs.js: -------------------------------------------------------------------------------- 1 | import nextPlugin from "@next/eslint-plugin-next"; 2 | 3 | /** @type {Awaited} */ 4 | export default [ 5 | { 6 | files: ["**/*.ts", "**/*.tsx"], 7 | plugins: { 8 | "@next/next": nextPlugin, 9 | }, 10 | rules: { 11 | ...nextPlugin.configs.recommended.rules, 12 | ...nextPlugin.configs["core-web-vitals"].rules, 13 | // TypeError: context.getAncestors is not a function 14 | "@next/next/no-duplicate-head": "off", 15 | }, 16 | }, 17 | ]; 18 | -------------------------------------------------------------------------------- /apps/web/src/app/(pages)/(dashboard)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { CustomToaster } from "@cooper/ui"; 2 | 3 | import HeaderLayout from "~/app/_components/header/header-layout"; 4 | import OnboardingWrapper from "~/app/_components/onboarding/onboarding-wrapper"; 5 | 6 | export default function RootLayout({ 7 | children, 8 | }: { 9 | children: React.ReactNode; 10 | }) { 11 | return ( 12 | 13 | 14 | {children} 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/auth/login-button-client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Image from "next/image"; 4 | 5 | import { handleGoogleSignIn } from "./actions"; 6 | 7 | export default function LoginButtonClient() { 8 | return ( 9 |
10 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/preview-migration.yml: -------------------------------------------------------------------------------- 1 | name: Migration 2 | 3 | on: 4 | pull_request: 5 | branches: ["main"] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 10 | 11 | jobs: 12 | migrate: 13 | runs-on: ubuntu-latest 14 | env: 15 | POSTGRES_URL: ${{ secrets.POSTGRES_URL_STAGING }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup 20 | uses: ./tooling/github/setup 21 | 22 | - name: Migrate 23 | run: pnpm db:migrate 24 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/onboarding/post-onboarding/coop-prompt.tsx: -------------------------------------------------------------------------------- 1 | import { WelcomeDialog } from "~/app/_components/onboarding/post-onboarding/welcome-dialog"; 2 | 3 | interface CoopPromptProps { 4 | firstName: string; 5 | onClick: () => void; 6 | } 7 | 8 | export function CoopPrompt({ firstName, onClick }: CoopPromptProps) { 9 | return ( 10 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/auth-proxy/.output/server/chunks/routes/r/_...auth_.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"_...auth_.mjs","sources":["../../../../../routes/r/[...auth].ts"],"sourcesContent":null,"names":[],"mappings":";;;;;;;;;AAIA,kBAAe,YAAA;AAAA,EAAa,OAAO,KAAA,KACjC,IAAK,CAAA,YAAA,CAAa,KAAK,CAAG,EAAA;AAAA,IACxB,QAAU,EAAA,IAAA;AAAA,IACV,MAAA,EAAQ,QAAQ,GAAI,CAAA,WAAA;AAAA,IACpB,SAAW,EAAA,CAAC,CAAC,OAAA,CAAQ,GAAI,CAAA,MAAA;AAAA,IACzB,gBAAA,EAAkB,QAAQ,GAAI,CAAA,uBAAA;AAAA,IAC9B,SAAW,EAAA;AAAA,MACT,MAAO,CAAA;AAAA,QACL,QAAA,EAAU,QAAQ,GAAI,CAAA,cAAA;AAAA,QACtB,YAAA,EAAc,QAAQ,GAAI,CAAA,kBAAA;AAAA,OAC3B,CAAA;AAAA,KACH;AAAA,GACD,CAAA;AACH,CAAA;;;;"} -------------------------------------------------------------------------------- /apps/docs/docs/05-next-steps.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Next Steps 6 | 7 | Now that you have some familiarity with the Cooper codebase, try to make some changes to the codebase / add new features! 8 | 9 | One thing to always remember is that while this documentation is a great resource for you to familiarize yourself with the project, it _will_ become outdated as the project evolves rapidly. 10 | 11 | It is **your** responsiblity to circle back to the documentation and update it as you make changes to the codebase :unclesampointing: 12 | 13 | Good luck and happy coding! :tada: 14 | -------------------------------------------------------------------------------- /turbo/generators/templates/package.json.hbs: -------------------------------------------------------------------------------- 1 | { "name": "@cooper/{{name}}", "private": true, "version": "0.1.0", "type": 2 | "module", "exports": { ".": "./src/index.ts" }, "license": "MIT", "scripts": { 3 | "clean": "rm -rf .turbo node_modules", "format": "prettier --check . 4 | --ignore-path ../../.gitignore", "lint": "eslint", "typecheck": "tsc --noEmit" 5 | }, "devDependencies": { "@cooper/eslint-config": "workspace:*", 6 | "@cooper/prettier-config": "workspace:*", "@cooper/tsconfig": "workspace:*", 7 | "eslint": "catalog:", "prettier": "catalog:", "typescript": "catalog:" }, 8 | "prettier": "@cooper/prettier-config" } -------------------------------------------------------------------------------- /apps/docs/docs/04-web/ui.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # UI 6 | 7 | We make extensive use of [shadcn/ui](https://ui.shadcn.com/) on Cooper to build components. 8 | 9 | All of the components are built using the `ui` package. We either directly use the components from the package or consume them to make new components, specific to the Cooper webapp. 10 | 11 | ## Adding a New Component 12 | 13 | To add a new component, you can make use of the `ui-add` command. This command will create the necessary files for a new component in the `ui` package. 14 | 15 | ```bash 16 | pnpm ui-add 17 | ``` 18 | -------------------------------------------------------------------------------- /apps/web/public/svg/compareAdd.svg: -------------------------------------------------------------------------------- 1 | 8 | 16 | 22 | -------------------------------------------------------------------------------- /apps/web/src/app/(pages)/(dashboard)/redirection/page.tsx: -------------------------------------------------------------------------------- 1 | export default function ErrorPage() { 2 | return ( 3 |
4 |
5 |
6 |

7 | Authentication Error 8 |

9 |

You must log in with husky.neu.edu

10 |

Click the sign in button to try again

11 |
12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /tooling/eslint/react.js: -------------------------------------------------------------------------------- 1 | import reactPlugin from "eslint-plugin-react"; 2 | import hooksPlugin from "eslint-plugin-react-hooks"; 3 | 4 | /** @type {Awaited} */ 5 | export default [ 6 | { 7 | files: ["**/*.ts", "**/*.tsx"], 8 | plugins: { 9 | react: reactPlugin, 10 | "react-hooks": hooksPlugin, 11 | }, 12 | rules: { 13 | ...reactPlugin.configs["jsx-runtime"].rules, 14 | ...hooksPlugin.configs.recommended.rules, 15 | }, 16 | languageOptions: { 17 | globals: { 18 | React: "writable", 19 | }, 20 | }, 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /apps/auth-proxy/routes/r/[...auth].ts: -------------------------------------------------------------------------------- 1 | import { Auth } from "@auth/core"; 2 | import Google from "@auth/core/providers/google"; 3 | import { eventHandler, toWebRequest } from "h3"; 4 | 5 | export default eventHandler(async (event) => 6 | Auth(toWebRequest(event), { 7 | basePath: "/r", 8 | secret: process.env.AUTH_SECRET, 9 | trustHost: !!process.env.VERCEL, 10 | redirectProxyUrl: process.env.AUTH_REDIRECT_PROXY_URL, 11 | providers: [ 12 | Google({ 13 | clientId: process.env.AUTH_GOOGLE_ID, 14 | clientSecret: process.env.AUTH_GOOGLE_SECRET, 15 | }), 16 | ], 17 | }), 18 | ); 19 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/form/sections/index.ts: -------------------------------------------------------------------------------- 1 | import { BasicInfoSection } from "~/app/_components/form/sections/basic-info-section"; 2 | import { ReviewSection } from "~/app/_components/form/sections//review-section"; 3 | import { CompanyDetailsSection } from "~/app/_components/form/sections/company-details-section"; 4 | import { InterviewSection } from "~/app/_components/form/sections/interview-section"; 5 | import { PaySection } from "~/app/_components/form/sections/pay-section"; 6 | 7 | export { 8 | BasicInfoSection, 9 | ReviewSection, 10 | CompanyDetailsSection, 11 | InterviewSection, 12 | PaySection, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/api/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { authRouter } from "./auth"; 2 | import { companyRouter } from "./company"; 3 | import { companyToLocationRouter } from "./companytoLocation"; 4 | import { locationRouter } from "./location"; 5 | import { profileRouter } from "./profile"; 6 | import { reviewRouter } from "./review"; 7 | import { roleRouter } from "./role"; 8 | import { roleAndCompanyRouter } from "./roleAndCompany"; 9 | 10 | export { 11 | authRouter, 12 | companyRouter, 13 | profileRouter, 14 | reviewRouter, 15 | roleRouter, 16 | locationRouter, 17 | companyToLocationRouter, 18 | roleAndCompanyRouter, 19 | }; 20 | -------------------------------------------------------------------------------- /apps/web/src/app/(pages)/(protected)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | import { auth } from "@cooper/auth"; 4 | import { CustomToaster } from "@cooper/ui"; 5 | import HeaderLayout from "~/app/_components/header/header-layout"; 6 | 7 | export default async function ProtectedLayour({ 8 | children, 9 | }: { 10 | children: React.ReactNode; 11 | }) { 12 | // Ensure user is authenticated 13 | const session = await auth(); 14 | 15 | if (!session) { 16 | redirect("/"); 17 | } 18 | 19 | return ( 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # docs 9 | .docusaurus 10 | .cache-loader 11 | 12 | 13 | # testing 14 | coverage 15 | 16 | # next.js 17 | .next/ 18 | out/ 19 | next-env.d.ts 20 | 21 | # production 22 | build 23 | 24 | # misc 25 | .DS_Store 26 | *.pem 27 | 28 | # debug 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | .pnpm-debug.log* 33 | 34 | # local env files 35 | .env 36 | .env*.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # typescript 42 | *.tsbuildinfo 43 | dist/ 44 | 45 | # turbo 46 | .turbo 47 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/auth/logout-button.tsx: -------------------------------------------------------------------------------- 1 | import { signOut } from "@cooper/auth"; 2 | import { Button } from "@cooper/ui/button"; 3 | 4 | export default function LogoutButton() { 5 | return ( 6 |
7 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/loading-results.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@cooper/ui"; 2 | 3 | import BodyLogo from "./body-logo"; 4 | 5 | export default function LoadingResults({ className }: { className?: string }) { 6 | return ( 7 |
13 | 14 |
15 |

Loading ...

16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/companies/company-about.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type { CompanyType } from "@cooper/db/schema"; 4 | 5 | interface CompanyAboutProps { 6 | className?: string; 7 | companyObj: CompanyType | undefined; 8 | } 9 | 10 | export function CompanyAbout({ companyObj }: CompanyAboutProps) { 11 | return ( 12 |
13 |
14 | About {companyObj?.name} 15 |
16 |
17 |

{companyObj?.description}

18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/onboarding/post-onboarding/browse-around-prompt.tsx: -------------------------------------------------------------------------------- 1 | import { WelcomeDialog } from "~/app/_components/onboarding/post-onboarding/welcome-dialog"; 2 | 3 | interface BrowseAroundPromptProps { 4 | firstName: string; 5 | onClick: () => void; 6 | } 7 | 8 | export function BrowseAroundPrompt({ 9 | firstName, 10 | onClick, 11 | }: BrowseAroundPromptProps) { 12 | return ( 13 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/auth/src/index.rsc.ts: -------------------------------------------------------------------------------- 1 | import { cache } from "react"; 2 | import NextAuth from "next-auth"; 3 | 4 | import { authConfig } from "./config"; 5 | 6 | export type { Session } from "next-auth"; 7 | 8 | const { handlers, auth: defaultAuth, signIn, signOut } = NextAuth(authConfig); 9 | 10 | /** 11 | * This is the main way to get session data for your RSCs. 12 | * This will de-duplicate all calls to next-auth's default `auth()` function and only call it once per request 13 | */ 14 | const auth = cache(defaultAuth); 15 | 16 | export { handlers, auth, signIn, signOut }; 17 | 18 | export { 19 | invalidateSessionToken, 20 | validateToken, 21 | isSecureContext, 22 | } from "./config"; 23 | -------------------------------------------------------------------------------- /packages/api/src/root.ts: -------------------------------------------------------------------------------- 1 | import { 2 | authRouter, 3 | companyRouter, 4 | companyToLocationRouter, 5 | locationRouter, 6 | profileRouter, 7 | reviewRouter, 8 | roleAndCompanyRouter, 9 | roleRouter, 10 | } from "./router"; 11 | import { createTRPCRouter } from "./trpc"; 12 | 13 | export const appRouter = createTRPCRouter({ 14 | auth: authRouter, 15 | company: companyRouter, 16 | role: roleRouter, 17 | profile: profileRouter, 18 | review: reviewRouter, 19 | location: locationRouter, 20 | companyToLocation: companyToLocationRouter, 21 | roleAndCompany: roleAndCompanyRouter, 22 | }); 23 | 24 | // export type definition of API 25 | export type AppRouter = typeof appRouter; 26 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/reviews/info-card.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from "react"; 2 | import React from "react"; 3 | 4 | interface InfoCardProps { 5 | title: string; 6 | children: ReactNode; 7 | } 8 | 9 | const InfoCard: React.FC = ({ title, children }) => { 10 | return ( 11 |
12 |
13 | {title} 14 |
15 |
{children}
16 |
17 | ); 18 | }; 19 | 20 | export default InfoCard; 21 | -------------------------------------------------------------------------------- /packages/db/src/schema/sessions.ts: -------------------------------------------------------------------------------- 1 | import { relations } from "drizzle-orm"; 2 | import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core"; 3 | 4 | import { User } from "./users"; 5 | 6 | export const Session = pgTable("session", { 7 | sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(), 8 | userId: uuid("userId") 9 | .notNull() 10 | .references(() => User.id, { onDelete: "cascade" }), 11 | expires: timestamp("expires", { 12 | mode: "date", 13 | withTimezone: true, 14 | }).notNull(), 15 | }); 16 | 17 | export const SessionRelations = relations(Session, ({ one }) => ({ 18 | user: one(User, { fields: [Session.userId], references: [User.id] }), 19 | })); 20 | -------------------------------------------------------------------------------- /tooling/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cooper/prettier-config", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "exports": { 7 | ".": "./index.js" 8 | }, 9 | "scripts": { 10 | "clean": "rm -rf .turbo node_modules", 11 | "format": "prettier --check . --ignore-path ../../.gitignore", 12 | "typecheck": "tsc --noEmit" 13 | }, 14 | "dependencies": { 15 | "@ianvs/prettier-plugin-sort-imports": "^4.3.0", 16 | "prettier": "catalog:", 17 | "prettier-plugin-tailwindcss": "^0.6.5" 18 | }, 19 | "devDependencies": { 20 | "@cooper/tsconfig": "workspace:*", 21 | "typescript": "catalog:" 22 | }, 23 | "prettier": "@cooper/prettier-config" 24 | } 25 | -------------------------------------------------------------------------------- /apps/auth-proxy/.nitro/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "forceConsistentCasingInFileNames": true, 4 | "strict": false, 5 | "noEmit": true, 6 | "target": "ESNext", 7 | "module": "ESNext", 8 | "moduleResolution": "Bundler", 9 | "allowJs": true, 10 | "resolveJsonModule": true, 11 | "jsx": "preserve", 12 | "allowSyntheticDefaultImports": true, 13 | "jsxFactory": "h", 14 | "jsxFragmentFactory": "Fragment", 15 | "paths": { 16 | "#imports": ["./nitro-imports"], 17 | "~/*": ["../../*"], 18 | "@/*": ["../../*"], 19 | "~~/*": ["../../*"], 20 | "@@/*": ["../../*"] 21 | } 22 | }, 23 | "include": ["./nitro.d.ts", "../../**/*"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/scraper/scraped/seed data/add_id.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import uuid 3 | 4 | # Input and output file paths 5 | input_file = 'location.csv' 6 | output_file = 'output_with_uuid.csv' 7 | 8 | # Open the input file for reading and output file for writing 9 | with open(input_file, mode='r', newline='', encoding='utf-8') as infile, \ 10 | open(output_file, mode='w', newline='', encoding='utf-8') as outfile: 11 | 12 | reader = csv.reader(infile) 13 | writer = csv.writer(outfile) 14 | 15 | # Append UUID to each row 16 | for row in reader: 17 | row_with_uuid = [str(uuid.uuid4())] + row 18 | writer.writerow(row_with_uuid) 19 | 20 | print(f"UUIDs added to each row. Output written to {output_file}") -------------------------------------------------------------------------------- /packages/api/src/router/auth.ts: -------------------------------------------------------------------------------- 1 | import type { TRPCRouterRecord } from "@trpc/server"; 2 | 3 | import { invalidateSessionToken } from "@cooper/auth"; 4 | 5 | import { protectedProcedure, publicProcedure } from "../trpc"; 6 | 7 | export const authRouter = { 8 | getSession: publicProcedure.query(({ ctx }) => { 9 | return ctx.session; 10 | }), 11 | getSecretMessage: protectedProcedure.query(() => { 12 | return "you can see this secret message!"; 13 | }), 14 | signOut: protectedProcedure.mutation(async (opts) => { 15 | if (!opts.ctx.token) { 16 | return { success: false }; 17 | } 18 | await invalidateSessionToken(opts.ctx.token); 19 | return { success: true }; 20 | }), 21 | } satisfies TRPCRouterRecord; 22 | -------------------------------------------------------------------------------- /packages/auth/env.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-properties */ 2 | import { createEnv } from "@t3-oss/env-nextjs"; 3 | import { z } from "zod"; 4 | 5 | export const env = createEnv({ 6 | server: { 7 | AUTH_GOOGLE_ID: z.string().min(1), 8 | AUTH_GOOGLE_SECRET: z.string().min(1), 9 | AUTH_SECRET: 10 | process.env.NODE_ENV === "production" 11 | ? z.string().min(1) 12 | : z.string().min(1).optional(), 13 | NODE_ENV: z.enum(["development", "production"]).optional(), 14 | VERCEL_ENV: z.enum(["development", "preview", "production"]).optional(), 15 | }, 16 | client: {}, 17 | experimental__runtimeEnv: {}, 18 | skipValidation: 19 | !!process.env.CI || process.env.npm_lifecycle_event === "lint", 20 | }); 21 | -------------------------------------------------------------------------------- /packages/scraper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cooper/scraper", 3 | "version": "0.1.0", 4 | "private": true, 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "", 8 | "license": "MIT", 9 | "scripts": { 10 | "company_data": "npx ts-node src/levels_company_data.ts", 11 | "company_name": "npx ts-node src/levels_company_names.ts" 12 | }, 13 | "dependencies": { 14 | "puppeteer": "^24.3.1" 15 | }, 16 | "devDependencies": { 17 | "@cooper/eslint-config": "workspace:*", 18 | "@cooper/prettier-config": "workspace:*", 19 | "@cooper/tsconfig": "workspace:*", 20 | "eslint": "catalog:", 21 | "prettier": "catalog:", 22 | "typescript": "catalog:" 23 | }, 24 | "prettier": "@cooper/prettier-config" 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/themed/onboarding/form.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | FormLabel as FormLabelPrimitive, 3 | FormMessage as FormMessagePrimitive, 4 | } from "@cooper/ui/form"; 5 | 6 | export function FormLabel({ 7 | children, 8 | required, 9 | ...props 10 | }: React.ComponentProps & { required?: boolean }) { 11 | return ( 12 | 13 | {children} 14 | {required && *} 15 | 16 | ); 17 | } 18 | 19 | export function FormMessage( 20 | props: React.ComponentProps, 21 | ) { 22 | return ; 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/public/svg/compareRole.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/web/public/svg/reviewReport.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/onboarding/onboarding-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from "react"; 2 | 3 | import { auth } from "@cooper/auth"; 4 | 5 | import { OnboardingDialog } from "~/app/_components/onboarding/dialog"; 6 | 7 | interface OnboardingWrapperProps { 8 | children: ReactNode; 9 | } 10 | 11 | /** 12 | * OnboardingWrapper component that wraps the app and initiates the onboarding dialog. 13 | * @param children - The children components 14 | * @returns The OnboardingWrapper component 15 | */ 16 | export default async function OnboardingWrapper({ 17 | children, 18 | }: OnboardingWrapperProps) { 19 | const session = await auth(); 20 | 21 | return ( 22 | <> 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/public/svg/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/web/src/app/styles/font.ts: -------------------------------------------------------------------------------- 1 | import localFont from "next/font/local"; 2 | 3 | export const hankenGroteskFont = localFont({ 4 | src: [ 5 | { 6 | path: "./../../../public/fonts/hanken-grotesk/HankenGrotesk-Light.ttf", 7 | weight: "200", 8 | style: "normal", 9 | }, 10 | { 11 | path: "./../../../public/fonts/hanken-grotesk/HankenGrotesk-Regular.ttf", 12 | weight: "400", 13 | style: "normal", 14 | }, 15 | { 16 | path: "./../../../public/fonts/hanken-grotesk/HankenGrotesk-Medium.ttf", 17 | weight: "600", 18 | style: "normal", 19 | }, 20 | { 21 | path: "./../../../public/fonts/hanken-grotesk/HankenGrotesk-Bold.ttf", 22 | weight: "800", 23 | style: "normal", 24 | }, 25 | ], 26 | variable: "--font-sans", 27 | }); 28 | -------------------------------------------------------------------------------- /apps/web/src/trpc/query-client.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defaultShouldDehydrateQuery, 3 | QueryClient, 4 | } from "@tanstack/react-query"; 5 | import SuperJSON from "superjson"; 6 | 7 | export const createQueryClient = () => 8 | new QueryClient({ 9 | defaultOptions: { 10 | queries: { 11 | // With SSR, we usually want to set some default staleTime 12 | // above 0 to avoid refetching immediately on the client 13 | staleTime: 30 * 1000, 14 | }, 15 | dehydrate: { 16 | serializeData: SuperJSON.serialize, 17 | shouldDehydrateQuery: (query) => 18 | defaultShouldDehydrateQuery(query) || 19 | query.state.status === "pending", 20 | }, 21 | hydrate: { 22 | deserializeData: SuperJSON.deserialize, 23 | }, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /packages/db/src/schema/users.ts: -------------------------------------------------------------------------------- 1 | import { relations } from "drizzle-orm"; 2 | import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core"; 3 | 4 | import { Account } from "./accounts"; 5 | import { Profile } from "./profiles"; 6 | 7 | export const User = pgTable("user", { 8 | id: uuid("id").notNull().primaryKey().defaultRandom(), 9 | name: varchar("name", { length: 255 }), 10 | email: varchar("email", { length: 255 }).notNull(), 11 | emailVerified: timestamp("emailVerified", { 12 | mode: "date", 13 | withTimezone: true, 14 | }), 15 | image: varchar("image", { length: 255 }), 16 | }); 17 | 18 | export const UserRelations = relations(User, ({ one, many }) => ({ 19 | accounts: many(Account), 20 | profile: one(Profile, { 21 | fields: [User.id], 22 | references: [Profile.userId], 23 | }), 24 | })); 25 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/reviews/review-search-bar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Input } from "../themed/onboarding/input"; 4 | 5 | interface ReviewSearchBarProps { 6 | searchTerm: string; 7 | onSearchChange: (value: string) => void; 8 | className?: string; 9 | } 10 | 11 | export default function ReviewSearchBar({ 12 | searchTerm, 13 | onSearchChange, 14 | className, 15 | }: ReviewSearchBarProps) { 16 | return ( 17 |
18 | { 21 | onSearchChange(e.target.value); 22 | }} 23 | className="!h-10 w-full !border-[0.75px] !border-cooper-gray-400 bg-cooper-gray-100 !text-sm focus:ring-1 focus:ring-cooper-gray-400 focus:ring-offset-0" 24 | placeholder="Search reviews..." 25 | /> 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /apps/web/src/utils/dateHelpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Format the date according to designs in Figma. 3 | * @param date Date object to format 4 | * @returns Date in format of 'dd mth yyyy' 5 | */ 6 | export function formatDate(date?: Date) { 7 | if (!date) { 8 | return ""; 9 | } 10 | 11 | const months = [ 12 | "Jan", 13 | "Feb", 14 | "Mar", 15 | "Apr", 16 | "May", 17 | "Jun", 18 | "Jul", 19 | "Aug", 20 | "Sep", 21 | "Oct", 22 | "Nov", 23 | "Dec", 24 | ]; 25 | 26 | // Extract the year, month, and day components 27 | const year = date.getFullYear(); 28 | const monthIndex = date.getMonth(); 29 | const month = months[monthIndex]; 30 | const day = String(date.getDate()).padStart(2, "0"); // Ensure two digits for the day 31 | 32 | // Return the formatted date string 33 | return `${day} ${month} ${year}`; 34 | } 35 | -------------------------------------------------------------------------------- /packages/ui/src/chip.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@cooper/ui"; 4 | import { Button } from "@cooper/ui/button"; 5 | 6 | interface ChipProps { 7 | label: string; 8 | onClick?: () => void; 9 | selected?: boolean; 10 | } 11 | 12 | const Chip = React.forwardRef( 13 | ({ label, onClick, selected = false }: ChipProps, ref) => { 14 | return ( 15 | 27 | ); 28 | }, 29 | ); 30 | 31 | export { Chip }; 32 | -------------------------------------------------------------------------------- /tooling/typescript/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | /** Base Options */ 5 | "esModuleInterop": true, 6 | "skipLibCheck": true, 7 | "target": "ES2022", 8 | "lib": ["ES2022"], 9 | "allowJs": true, 10 | "resolveJsonModule": true, 11 | "moduleDetection": "force", 12 | "isolatedModules": true, 13 | 14 | /** Keep TSC performant in monorepos */ 15 | "incremental": true, 16 | "disableSourceOfProjectReferenceRedirect": true, 17 | 18 | /** Strictness */ 19 | "strict": true, 20 | "noUncheckedIndexedAccess": true, 21 | "checkJs": true, 22 | 23 | /** Transpile using Bundler (not tsc) */ 24 | "module": "Preserve", 25 | "moduleResolution": "Bundler", 26 | "noEmit": true 27 | }, 28 | "exclude": ["node_modules", "build", "dist", ".next"] 29 | } 30 | -------------------------------------------------------------------------------- /apps/auth-proxy/.output/server/chunks/routes/r/_...auth_.mjs: -------------------------------------------------------------------------------- 1 | import { Auth } from "@auth/core"; 2 | import Google from "@auth/core/providers/google"; 3 | import { e as eventHandler, t as toWebRequest } from "../../runtime.mjs"; 4 | import "node:http"; 5 | import "node:https"; 6 | import "node:fs"; 7 | import "node:path"; 8 | import "node:url"; 9 | 10 | const ____auth_ = eventHandler(async (event) => 11 | Auth(toWebRequest(event), { 12 | basePath: "/r", 13 | secret: process.env.AUTH_SECRET, 14 | trustHost: !!process.env.VERCEL, 15 | redirectProxyUrl: process.env.AUTH_REDIRECT_PROXY_URL, 16 | providers: [ 17 | Google({ 18 | clientId: process.env.AUTH_GOOGLE_ID, 19 | clientSecret: process.env.AUTH_GOOGLE_SECRET, 20 | }), 21 | ], 22 | }), 23 | ); 24 | 25 | export { ____auth_ as default }; 26 | //# sourceMappingURL=_...auth_.mjs.map 27 | -------------------------------------------------------------------------------- /.github/workflows/migration.yml: -------------------------------------------------------------------------------- 1 | name: Migration 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 10 | 11 | jobs: 12 | migrate: 13 | runs-on: ubuntu-latest 14 | env: 15 | POSTGRES_URL: ${{ secrets.POSTGRES_URL }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup 20 | uses: ./tooling/github/setup 21 | 22 | - name: Migrate 23 | run: pnpm db:migrate 24 | 25 | migrate-staging: 26 | runs-on: ubuntu-latest 27 | env: 28 | POSTGRES_URL: ${{ secrets.POSTGRES_URL_STAGING }} 29 | steps: 30 | - uses: actions/checkout@v4 31 | 32 | - name: Setup 33 | uses: ./tooling/github/setup 34 | 35 | - name: Migrate Staging 36 | run: pnpm db:migrate 37 | -------------------------------------------------------------------------------- /packages/ui/src/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type { VariantProps } from "class-variance-authority"; 4 | import * as React from "react"; 5 | import * as LabelPrimitive from "@radix-ui/react-label"; 6 | import { cva } from "class-variance-authority"; 7 | 8 | import { cn } from "@cooper/ui"; 9 | 10 | const labelVariants = cva( 11 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 12 | ); 13 | 14 | const Label = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef & 17 | VariantProps 18 | >(({ className, ...props }, ref) => ( 19 | 24 | )); 25 | Label.displayName = LabelPrimitive.Root.displayName; 26 | 27 | export { Label }; 28 | -------------------------------------------------------------------------------- /apps/auth-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cooper/auth-proxy", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build": "nitro build", 7 | "clean": "git clean -xdf .cache .nitro .output .turbo .vercel node_modules", 8 | "lint": "eslint", 9 | "format": "prettier --check . --ignore-path ../../.gitignore", 10 | "typecheck": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@auth/core": "0.32.0" 14 | }, 15 | "devDependencies": { 16 | "@cooper/eslint-config": "workspace:*", 17 | "@cooper/prettier-config": "workspace:*", 18 | "@cooper/tailwind-config": "workspace:*", 19 | "@cooper/tsconfig": "workspace:*", 20 | "@types/node": "^20.14.15", 21 | "eslint": "catalog:", 22 | "h3": "^1.12.0", 23 | "nitropack": "^2.9.7", 24 | "prettier": "catalog:", 25 | "typescript": "catalog:" 26 | }, 27 | "prettier": "@cooper/prettier-config" 28 | } 29 | -------------------------------------------------------------------------------- /apps/docs/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; 2 | 3 | /** 4 | * Creating a sidebar enables you to: 5 | - create an ordered group of docs 6 | - render a sidebar for each doc of that group 7 | - provide next/previous navigation 8 | 9 | The sidebars can be generated from the filesystem, or explicitly defined here. 10 | 11 | Create as many sidebars as you want. 12 | */ 13 | const sidebars: SidebarsConfig = { 14 | // By default, Docusaurus generates a sidebar from the docs folder structure 15 | tutorialSidebar: [{ type: "autogenerated", dirName: "." }], 16 | 17 | // But you can create a sidebar manually 18 | /* 19 | tutorialSidebar: [ 20 | 'intro', 21 | 'hello', 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['tutorial-basics/create-a-document'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | export default sidebars; 32 | -------------------------------------------------------------------------------- /apps/web/public/svg/apartment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tooling/tailwind/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cooper/tailwind-config", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "exports": { 7 | "./native": "./native.ts", 8 | "./web": "./web.ts" 9 | }, 10 | "license": "MIT", 11 | "scripts": { 12 | "clean": "rm -rf .turbo node_modules", 13 | "format": "prettier --check . --ignore-path ../../.gitignore", 14 | "lint": "eslint", 15 | "typecheck": "tsc --noEmit" 16 | }, 17 | "dependencies": { 18 | "postcss": "^8.4.39", 19 | "tailwindcss": "^3.4.4", 20 | "tailwindcss-animate": "^1.0.7" 21 | }, 22 | "devDependencies": { 23 | "@cooper/eslint-config": "workspace:*", 24 | "@cooper/prettier-config": "workspace:*", 25 | "@cooper/tsconfig": "workspace:*", 26 | "eslint": "catalog:", 27 | "prettier": "catalog:", 28 | "typescript": "catalog:" 29 | }, 30 | "prettier": "@cooper/prettier-config" 31 | } 32 | -------------------------------------------------------------------------------- /apps/web/src/app/_components/onboarding/post-onboarding/welcome-dialog.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | import { Button } from "@cooper/ui/button"; 4 | 5 | interface WelcomeDialogProps { 6 | heading: string; 7 | subheading: string; 8 | buttonText: string; 9 | onClick: () => void; 10 | } 11 | 12 | export function WelcomeDialog({ 13 | heading, 14 | subheading, 15 | buttonText, 16 | onClick, 17 | }: WelcomeDialogProps) { 18 | return ( 19 |
20 | Cooper Logo 21 |

22 | {heading} 23 |

24 |

25 | {subheading} 26 |

27 | 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /packages/ui/src/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useToast } from "./hooks/use-toast"; 4 | import { 5 | Toast, 6 | ToastClose, 7 | ToastDescription, 8 | ToastProvider, 9 | ToastTitle, 10 | ToastViewport, 11 | } from "./toast"; 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast(); 15 | 16 | return ( 17 | 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | 21 |
22 | {title && {title}} 23 | {description && ( 24 | {description} 25 | )} 26 |
27 | {action} 28 | 29 |
30 | ); 31 | })} 32 | 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /packages/validators/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cooper/validators", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "exports": { 7 | ".": { 8 | "types": "./dist/index.d.ts", 9 | "default": "./src/index.ts" 10 | } 11 | }, 12 | "license": "MIT", 13 | "scripts": { 14 | "build": "tsc", 15 | "dev": "tsc --watch", 16 | "clean": "rm -rf .turbo dist node_modules", 17 | "format": "prettier --check . --ignore-path ../../.gitignore", 18 | "lint": "eslint", 19 | "typecheck": "tsc --noEmit --emitDeclarationOnly false" 20 | }, 21 | "dependencies": { 22 | "zod": "catalog:" 23 | }, 24 | "devDependencies": { 25 | "@cooper/eslint-config": "workspace:*", 26 | "@cooper/prettier-config": "workspace:*", 27 | "@cooper/tsconfig": "workspace:*", 28 | "eslint": "catalog:", 29 | "prettier": "catalog:", 30 | "typescript": "catalog:" 31 | }, 32 | "prettier": "@cooper/prettier-config" 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }], 5 | "eslint.useFlatConfig": true, 6 | "eslint.workingDirectories": [ 7 | { "pattern": "apps/*/" }, 8 | { "pattern": "packages/*/" }, 9 | { "pattern": "tooling/*/" } 10 | ], 11 | "prettier.ignorePath": ".gitignore", 12 | "tailwindCSS.experimental.classRegex": [ 13 | ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], 14 | ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] 15 | ], 16 | "tailwindCSS.experimental.configFile": "./tooling/tailwind/web.ts", 17 | "typescript.enablePromptUseWorkspaceTsdk": true, 18 | "typescript.preferences.autoImportFileExcludePatterns": [ 19 | "next/router.d.ts", 20 | "next/dist/client/router.d.ts" 21 | ], 22 | "typescript.tsdk": "node_modules/typescript/lib", 23 | "files.associations": { 24 | "*.json": "jsonc" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Since .env is gitignored, you can use .env.example to build a new `.env` file when you clone the repo. 2 | # Keep this file up-to-date when you add new variables to \`.env\`. 3 | 4 | # This file will be committed to version control, so make sure not to have any secrets in it. 5 | # If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets. 6 | 7 | # The database URL is used to connect to your Supabase database. 8 | POSTGRES_URL="postgresql://admin:admin@localhost:5432/cooper" 9 | 10 | # You can generate the secret via 'openssl rand -base64 32' on Unix 11 | # @see https://next-auth.js.org/configuration/options#secret 12 | AUTH_SECRET='supersecret' 13 | 14 | # Preconfigured Google OAuth provider, works out-of-the-box 15 | # @see https://next-auth.js.org/providers/google 16 | AUTH_GOOGLE_ID='' 17 | AUTH_GOOGLE_SECRET='' 18 | 19 | # In case you're using the Auth Proxy (apps/auth-proxy) 20 | # AUTH_REDIRECT_PROXY_URL='https://auth.your-server.com/r' -------------------------------------------------------------------------------- /packages/db/src/schema/locations.ts: -------------------------------------------------------------------------------- 1 | import { relations } from "drizzle-orm"; 2 | import { pgTable, uuid, varchar } from "drizzle-orm/pg-core"; 3 | import { createInsertSchema } from "drizzle-zod"; 4 | import { z } from "zod"; 5 | 6 | import { CompaniesToLocations } from "./companiesToLocations"; 7 | import { Review } from "./reviews"; 8 | 9 | export const Location = pgTable("location", { 10 | id: uuid("id").notNull().primaryKey().defaultRandom(), 11 | city: varchar("city").notNull(), 12 | state: varchar("state"), 13 | country: varchar("country").notNull(), 14 | }); 15 | 16 | export type LocationType = typeof Location.$inferSelect; 17 | 18 | export const LocationRelations = relations(Location, ({ many }) => ({ 19 | companies_to_locations: many(CompaniesToLocations), 20 | reviews: many(Review), 21 | })); 22 | 23 | export const CreateLocationSchema = createInsertSchema(Location, { 24 | city: z.string(), 25 | state: z.string(), 26 | country: z.string(), 27 | }).omit({ 28 | id: true, 29 | }); 30 | -------------------------------------------------------------------------------- /apps/web/public/svg/magnifyingGlass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/web/public/svg/work.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/ui/src/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@cooper/ui"; 4 | 5 | export type TextareaProps = 6 | React.TextareaHTMLAttributes & { 7 | variant?: "default" | "dialogue"; 8 | }; 9 | 10 | const Textarea = React.forwardRef( 11 | ({ className, variant, ...props }, ref) => { 12 | const style = 13 | variant === "dialogue" 14 | ? "flex h-fit w-[100%] rounded-lg outline outline-[1px] outline-[#474747] px-3 py-2" 15 | : "flex min-h-[200px] w-full rounded-md border-[3px] border-cooper-blue-600 bg-white px-3 py-2 text-xl font-normal ring-offset-background placeholder:text-cooper-gray-600 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"; 16 | 17 | return