├── components ├── fonts │ ├── Montserrat-Black.ttf:Zone.Identifier │ ├── Montserrat-Bold.ttf:Zone.Identifier │ ├── Montserrat-Light.ttf:Zone.Identifier │ ├── Montserrat-Thin.ttf:Zone.Identifier │ ├── Montserrat-BoldItalic.ttf:Zone.Identifier │ ├── Montserrat-ExtraBold.ttf:Zone.Identifier │ ├── Montserrat-ExtraLight.ttf:Zone.Identifier │ ├── Montserrat-Italic.ttf:Zone.Identifier │ ├── Montserrat-Medium.ttf:Zone.Identifier │ ├── Montserrat-Regular.ttf:Zone.Identifier │ ├── Montserrat-SemiBold.ttf:Zone.Identifier │ ├── Montserrat-ThinItalic.ttf:Zone.Identifier │ ├── Montserrat-BlackItalic.ttf:Zone.Identifier │ ├── Montserrat-ExtraBoldItalic.ttf:Zone.Identifier │ ├── Montserrat-LightItalic.ttf:Zone.Identifier │ ├── Montserrat-MediumItalic.ttf:Zone.Identifier │ ├── Montserrat-SemiBoldItalic.ttf:Zone.Identifier │ ├── Montserrat-ExtraLightItalic.ttf:Zone.Identifier │ ├── Montserrat-Black.ttf │ ├── Montserrat-Bold.ttf │ ├── Montserrat-Light.ttf │ ├── Montserrat-Thin.ttf │ ├── Montserrat-Italic.ttf │ ├── Montserrat-Medium.ttf │ ├── Montserrat-Regular.ttf │ ├── Montserrat-BoldItalic.ttf │ ├── Montserrat-ExtraBold.ttf │ ├── Montserrat-ExtraLight.ttf │ ├── Montserrat-SemiBold.ttf │ ├── Montserrat-ThinItalic.ttf │ ├── Montserrat-BlackItalic.ttf │ ├── Montserrat-LightItalic.ttf │ ├── Montserrat-MediumItalic.ttf │ ├── Montserrat-ExtraBoldItalic.ttf │ ├── Montserrat-SemiBoldItalic.ttf │ └── Montserrat-ExtraLightItalic.ttf ├── reviews │ ├── components │ │ ├── ReviewHero.tsx │ │ ├── ReviewHero.test.tsx │ │ └── Hero.test.tsx │ ├── read-reviews.test.tsx │ ├── ui │ │ └── searchbar.tsx │ └── review-table.test.tsx ├── landlord │ ├── LandlordBanner.tsx │ ├── LandlordBanner.test.tsx │ └── LandlordInfo.test.tsx ├── admin │ ├── sections │ │ ├── TenantResources.test.tsx │ │ ├── SuspiciousLandlords.test.tsx │ │ ├── DeletedReviews.test.tsx │ │ ├── Stats.tsx │ │ ├── FlaggedReviews.test.tsx │ │ ├── RecentReviews.tsx │ │ ├── FlaggedKeywords.test.tsx │ │ └── RecentReviews.test.tsx │ ├── components │ │ ├── StateStats.tsx │ │ └── StateStats.test.tsx │ └── types │ │ └── types.ts ├── privacy │ └── Privacy.test.tsx ├── city │ ├── CitiesTable.test.tsx │ ├── CityInfo.test.tsx │ ├── CityMobileFilters.test.tsx │ ├── CityFilters.test.tsx │ └── CityPage.test.tsx ├── layout │ ├── layout.tsx │ ├── links.tsx │ ├── MobileNav.test.tsx │ ├── navbar.test.tsx │ ├── MobileNav.tsx │ ├── footer.test.tsx │ ├── layout.test.tsx │ └── footer.tsx ├── svg │ ├── icons │ │ ├── transparency.tsx │ │ ├── privacy.tsx │ │ └── Solidarity.tsx │ ├── social │ │ └── github.tsx │ └── logo │ │ └── logo.tsx ├── modal │ ├── SpamReviewModal.test.tsx │ └── success-modal.test.tsx ├── about │ ├── faq.test.tsx │ ├── revenue.tsx │ ├── contributing.tsx │ ├── contact.tsx │ ├── aboutUs.test.tsx │ ├── privacy.tsx │ ├── privacy.test.tsx │ ├── aboutUs.tsx │ ├── moderation.tsx │ ├── contact.test.tsx │ ├── moderation.test.tsx │ ├── revenue.test.tsx │ └── contributing.test.tsx ├── ui │ ├── link-button-lg.tsx │ ├── link-button-light-lg.tsx │ ├── button-light.tsx │ ├── CloseButton.tsx │ ├── RatingStars.tsx │ ├── Spinner.tsx │ ├── CloseButton.test.tsx │ ├── button.tsx │ ├── ChangeLanguage.test.tsx │ ├── button-light.test.tsx │ ├── link-button-lg.test.tsx │ ├── StateSelector.test.tsx │ ├── link-button-light-lg.test.tsx │ ├── LargeTextInput.tsx │ ├── TextInput.tsx │ └── button.test.tsx ├── PostHog │ └── PHProvider.tsx ├── analytics │ ├── analytics.test.tsx │ └── components │ │ └── sidebar.test.tsx ├── icons │ └── HouseIcon.test.tsx ├── resources │ ├── resourcesInfo.test.tsx │ ├── resourcesInfo.tsx │ ├── resource-mobile-filters.test.tsx │ └── ResourceList.test.tsx ├── home │ ├── icon-section.test.tsx │ ├── hero.test.tsx │ └── hero.tsx ├── Map │ ├── CustomMarker.tsx │ └── CustomMarker.test.tsx ├── poster │ ├── Poster.tsx │ └── Poster.test.tsx ├── zip │ └── ZipInfo.test.tsx ├── adsense │ └── Adsense.tsx └── create-review │ ├── ratings-radio.test.tsx │ └── components │ └── CreateReviewHero.tsx ├── .prettierignore ├── types ├── react-adsense.d.ts └── review.types.ts ├── public ├── Ads.txt ├── ads.txt ├── review.jpg ├── favicon.ico ├── friends.webp ├── poster_picture.webp ├── poster │ └── rtl_poster.pdf ├── review.jpg:Zone.Identifier ├── robots.txt ├── sitemap.xml ├── friends 1.webp:Zone.Identifier └── vercel.svg ├── bun.lockb ├── pages ├── api │ ├── auth │ │ └── [...auth0].tsx │ ├── flagged-keywords │ │ ├── get-flagged-keywords.tsx │ │ ├── add-flagged-keyword.tsx │ │ └── delete-flagged-keyword.tsx │ ├── review │ │ ├── state-info.tsx │ │ ├── get-landlords.tsx │ │ ├── state-city-info.tsx │ │ ├── review-analytics.tsx │ │ ├── review-analytics-chart.tsx │ │ ├── get-landlord.tsx │ │ ├── get-zip-stats.tsx │ │ ├── get-reviews.tsx │ │ ├── get-other-landlords.tsx │ │ ├── get-filter-options.tsx │ │ ├── get-landlord-suggestions.tsx │ │ ├── submit-review.tsx │ │ ├── delete-review.tsx │ │ ├── edit-review.tsx │ │ └── flag-review.tsx │ ├── suspicious-landlords │ │ ├── get-landlords.tsx │ │ ├── get-suspicious-landlord.tsx │ │ ├── add-suspicious-landlord.tsx │ │ ├── delete-suspicious-landlord.tsx │ │ └── edit-suspicious-landlord.tsx │ ├── tenant-resources │ │ ├── get-resources.tsx │ │ ├── delete-resource.tsx │ │ ├── add-resource.tsx │ │ └── edit-resource.tsx │ ├── admin │ │ ├── get-stats.tsx │ │ ├── get-flagged.tsx │ │ ├── get-recent.tsx │ │ └── get-deleted.tsx │ ├── force-revalidate.tsx │ ├── location │ │ └── get-location.tsx │ ├── user-update │ │ ├── delete.ts │ │ └── update.ts │ └── cron.ts ├── server-sitemap.xml │ └── index.ts ├── _document.tsx ├── 404.tsx └── 500.tsx ├── lib ├── location │ └── types.ts ├── auth │ └── constants.ts ├── db.ts ├── stats │ └── types.ts ├── flagged-keywords │ ├── types.ts │ ├── models │ │ └── flagged-keywords-data-layer.ts │ └── flagged-keywords.ts ├── suspicious-landlords │ ├── types.ts │ └── models │ │ └── suspicious-landlord-data-layer.ts ├── review │ ├── flagged.ts │ ├── models │ │ ├── user-report-review.ts │ │ ├── admin-delete-review.ts │ │ └── admin-update-review.ts │ ├── types │ │ ├── Responses.ts │ │ ├── Queries.ts │ │ └── review.ts │ └── user-codes.ts ├── tenant-resource │ ├── types.ts │ └── models │ │ └── resource.ts ├── analytics │ └── types.ts └── captcha │ └── verifyToken.ts ├── .husky └── pre-commit ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── publish-release.yml ├── postcss.config.mjs ├── migrations ├── 008-review.ts ├── 004-review.ts ├── 005-review.ts ├── 012-review.ts ├── 013-review.ts ├── 003-users.ts ├── 006-ca_location.ts ├── 007-us_location.ts ├── 014-review.ts ├── 009-recent_reviews.ts ├── 010-spam_landlord.ts ├── 011-keyword_flagging.ts ├── 001-users.ts ├── 002-tenant_resources.ts └── 000-review.ts ├── .infisical.json ├── prettier.config.mjs ├── .vscode ├── extensions.json └── settings.json ├── util ├── helpers │ ├── toTitleCase.ts │ ├── isWithinLastDay.ts │ ├── fetcher.ts │ ├── filter-options.ts │ ├── helper-functions.ts │ ├── getCountryCodes.ts │ ├── fetchFilterOptions.ts │ └── fetchReviews.ts ├── countries │ ├── unitedKingdom │ │ └── regions.json │ ├── australia │ │ └── territories.json │ ├── newZealand │ │ └── nz-provinces.json │ ├── canada │ │ └── provinces.json │ ├── ca-province-location.json │ ├── germany │ │ └── states.json │ └── norway │ │ └── counties.json ├── hooks │ ├── useDebounce.ts │ ├── useScreenWidth.ts │ └── useLandlordSuggestions.ts ├── cors.ts ├── rateLimit.ts └── reviewRateLimit.ts ├── knip.json ├── next-env.d.ts ├── tsconfig.jest.json ├── redux ├── hooks.ts └── store.ts ├── .dockerignore ├── messages ├── en-CA │ ├── layout.json │ ├── alerts.json │ ├── home.json │ ├── resources.json │ ├── filters.json │ └── support.json └── fr-CA │ ├── layout.json │ ├── alerts.json │ ├── home.json │ ├── filters.json │ └── resources.json ├── .prettierrc ├── next-sitemap.config.js ├── .gitignore ├── .env.example ├── next.config.js ├── check-tests.sh ├── Dockerfile ├── jest.config.js ├── setupTests.ts ├── eslint.config.mjs ├── tsconfig.json ├── tailwind.config.mjs └── test-utils.tsx /components/fonts/Montserrat-Black.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-Bold.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-Light.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-Thin.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-BoldItalic.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-ExtraBold.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-ExtraLight.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-Italic.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-Medium.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-Regular.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-SemiBold.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-ThinItalic.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-BlackItalic.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-ExtraBoldItalic.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-LightItalic.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-MediumItalic.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-SemiBoldItalic.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /types/react-adsense.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-adsense' 2 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-ExtraLightItalic.ttf:Zone.Identifier: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/Ads.txt: -------------------------------------------------------------------------------- 1 | google.com, pub-1233437669445756, DIRECT, f08c47fec0942fa0 -------------------------------------------------------------------------------- /public/ads.txt: -------------------------------------------------------------------------------- 1 | google.com, pub-1233437669445756, DIRECT, f08c47fec0942fa0 -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/bun.lockb -------------------------------------------------------------------------------- /public/review.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/public/review.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/friends.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/public/friends.webp -------------------------------------------------------------------------------- /public/poster_picture.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/public/poster_picture.webp -------------------------------------------------------------------------------- /pages/api/auth/[...auth0].tsx: -------------------------------------------------------------------------------- 1 | import { handleAuth } from '@auth0/nextjs-auth0' 2 | 3 | export default handleAuth() 4 | -------------------------------------------------------------------------------- /public/poster/rtl_poster.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/public/poster/rtl_poster.pdf -------------------------------------------------------------------------------- /lib/location/types.ts: -------------------------------------------------------------------------------- 1 | export interface IZipLocations { 2 | zip: string 3 | latitude: string 4 | longitude: string 5 | } 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./check-tests.sh 4 | bun run test:merge 5 | bunx lint-staged 6 | bun run format 7 | bun run knip -------------------------------------------------------------------------------- /lib/auth/constants.ts: -------------------------------------------------------------------------------- 1 | export const FAILED_TO_RETRIEVE_REVIEWS = 2 | 'Failed to retrieve existing reviews from the database.' 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Documentation 2 | # https://help.github.com/en/articles/about-code-owners 3 | 4 | * @kellenwiltshire -------------------------------------------------------------------------------- /components/fonts/Montserrat-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-Black.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-Light.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-Thin.ttf -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | } 6 | 7 | export default config 8 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-Italic.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-Medium.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /lib/db.ts: -------------------------------------------------------------------------------- 1 | import postgres from 'postgres' 2 | 3 | const sql = postgres(process.env.DATABASE_URL as string) 4 | 5 | export default sql 6 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-BoldItalic.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-ExtraBold.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-ExtraLight.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-SemiBold.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-ThinItalic.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-BlackItalic.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-LightItalic.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-MediumItalic.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /components/fonts/Montserrat-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /migrations/008-review.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | await DB` 3 | ALTER TABLE review 4 | ADD COLUMN moderator TEXT[]; 5 | ` 6 | } 7 | -------------------------------------------------------------------------------- /components/fonts/Montserrat-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RateTheLandlord/rtl-frontend/HEAD/components/fonts/Montserrat-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /migrations/004-review.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | await DB` 3 | ALTER TABLE review 4 | ADD COLUMN rent numeric DEFAULT NULL; 5 | ` 6 | } 7 | -------------------------------------------------------------------------------- /.infisical.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspaceId": "0f18438a-489e-4c6d-9cb6-40596cad96c8", 3 | "defaultEnvironment": "production", 4 | "gitBranchToEnvironmentMapping": null 5 | } 6 | -------------------------------------------------------------------------------- /migrations/005-review.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | await DB` 3 | ALTER TABLE review 4 | ADD COLUMN moderation_reason TEXT DEFAULT NULL; 5 | ` 6 | } 7 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://prettier.io/docs/en/configuration.html 3 | * @type {import("prettier").Config} 4 | */ 5 | const config = {} 6 | 7 | export default config 8 | -------------------------------------------------------------------------------- /types/review.types.ts: -------------------------------------------------------------------------------- 1 | export enum Country { 2 | IE = 'IE', 3 | GB = 'GB', 4 | AU = 'AU', 5 | US = 'US', 6 | NZ = 'NZ', 7 | DE = 'DE', 8 | NO = 'NO', 9 | CA = 'CA', 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "lokalise.i18n-ally", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "bradlc.vscode-tailwindcss" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /util/helpers/toTitleCase.ts: -------------------------------------------------------------------------------- 1 | export const toTitleCase = (str: string) => { 2 | return str.replace(/\w\S*/g, function (txt) { 3 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() 4 | }) 5 | } 6 | -------------------------------------------------------------------------------- /migrations/012-review.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | await DB` 3 | ALTER TABLE review 4 | ADD COLUMN delete_date TEXT, 5 | ADD COLUMN delete_reason TEXT, 6 | ADD COLUMN deleted_by TEXT[]; 7 | ` 8 | } 9 | -------------------------------------------------------------------------------- /migrations/013-review.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | await DB` 3 | ALTER TABLE review 4 | ADD COLUMN restore_date TEXT, 5 | ADD COLUMN restore_reason TEXT, 6 | ADD COLUMN restored_by TEXT[]; 7 | ` 8 | } 9 | -------------------------------------------------------------------------------- /knip.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["migrations/*", "*.config.*", "setupTests.ts", "test-utils.tsx"], 3 | "ignoreDependencies": ["sharp", "husky"], 4 | "ignoreBinaries": ["eslint"], 5 | "rules": { 6 | "devDependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/stats/types.ts: -------------------------------------------------------------------------------- 1 | export type CountryStats = Record< 2 | string, 3 | { total: number; states: { key: string; total: number }[] } 4 | > 5 | 6 | export interface TotalStats { 7 | total_reviews: number 8 | countryStats: CountryStats 9 | } 10 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /public/review.jpg:Zone.Identifier: -------------------------------------------------------------------------------- 1 | [ZoneTransfer] 2 | ZoneId=3 3 | ReferrerUrl=https://unsplash.com/ 4 | HostUrl=https://images.unsplash.com/photo-1552960562-daf630e9278b?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=jason-goodman-fznQW-kn5VU-unsplash.jpg&w=640 5 | -------------------------------------------------------------------------------- /lib/flagged-keywords/types.ts: -------------------------------------------------------------------------------- 1 | import { Keywords } from '@/util/interfaces/interfaces' 2 | 3 | export interface IResponse { 4 | status: number 5 | message: string 6 | } 7 | 8 | export interface getFlaggedKeywordsResponse { 9 | keywords: Keywords[] 10 | total: number 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.jest.json: -------------------------------------------------------------------------------- 1 | // Next requires "jsx" : "preserve", but Jest requires a JSX transform 2 | // This config extension overrides the "jsx" key which seems to play nice 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "jsx": "react-jsx" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /util/countries/unitedKingdom/regions.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"short": "ENG", "name": "England", "country": "UK"}, 3 | {"short": "NI", "name": "Northern Ireland", "country": "UK"}, 4 | {"short": "SC", "name": "Scotland", "country": "UK"}, 5 | {"short": "WA", "name": "Wales", "country": "UK"} 6 | ] 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.localesPaths": ["messages"], 3 | "i18n-ally.keystyle": "nested", 4 | "i18n-ally.extract.ignoredByFiles": { 5 | "components/landlord/OtherLandlord.tsx": ["Reviews / Landlord Link"] 6 | }, 7 | "i18n-ally.extract.ignored": ["ease-out duration-300"] 8 | } 9 | -------------------------------------------------------------------------------- /redux/hooks.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' 2 | import { RootState, AppDispatch } from './store' 3 | 4 | export const useAppDispatch = () => useDispatch() 5 | export const useAppSelector: TypedUseSelectorHook = useSelector 6 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # * 2 | User-agent: * 3 | Disallow: /404 4 | 5 | # * 6 | User-agent: * 7 | Allow: / 8 | 9 | # Host 10 | Host: https://ratethelandlord.org/ 11 | 12 | # Sitemaps 13 | Sitemap: https://ratethelandlord.org/sitemap.xml 14 | Sitemap: https://ratethelandlord.org/server-sitemap.xml 15 | -------------------------------------------------------------------------------- /lib/suspicious-landlords/types.ts: -------------------------------------------------------------------------------- 1 | import { SuspiciousLandlord } from '@/util/interfaces/interfaces' 2 | 3 | export interface IResponse { 4 | status: number 5 | message: string 6 | } 7 | 8 | export interface GetSuspiciousLandlordResponse { 9 | landlords: SuspiciousLandlord[] 10 | total: number 11 | } 12 | -------------------------------------------------------------------------------- /lib/review/flagged.ts: -------------------------------------------------------------------------------- 1 | import { Review } from '@/util/interfaces/interfaces' 2 | import sql from '../db' 3 | 4 | export async function getFlagged(): Promise { 5 | const reviews = await sql< 6 | Review[] 7 | >`SELECT * FROM review WHERE flagged = true AND delete_date IS NULL;` 8 | return reviews 9 | } 10 | -------------------------------------------------------------------------------- /util/helpers/isWithinLastDay.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | import isBetween from 'dayjs/plugin/isBetween' 3 | 4 | // Register the plugin 5 | dayjs.extend(isBetween) 6 | 7 | const isWithinLastDay = (lastAttempt: Date) => { 8 | return dayjs().diff(dayjs(lastAttempt), 'hour') < 24 9 | } 10 | 11 | export default isWithinLastDay 12 | -------------------------------------------------------------------------------- /lib/tenant-resource/types.ts: -------------------------------------------------------------------------------- 1 | export interface ResourceQuery { 2 | page?: number 3 | limit?: string 4 | search?: string 5 | sort?: 'az' | 'za' | 'new' | 'old' | 'high' | 'low' | undefined 6 | country?: string 7 | state?: string 8 | city?: string 9 | } 10 | 11 | export interface IResponse { 12 | status: number 13 | message: string 14 | } 15 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .tmp/ 2 | .cache/ 3 | .git/ 4 | build/ 5 | .gitignore 6 | .next/ 7 | .dockerignore 8 | Dockerfile 9 | docker-compose.yml 10 | .github/ 11 | .husky/ 12 | node_modules/ 13 | 14 | 15 | coverage 16 | LICENSE 17 | README.md 18 | CONTRIBUTING.md 19 | CODE_OF_CONDUCT.md 20 | .github 21 | .vscode 22 | npm-debug.log 23 | npm-debug.log.* 24 | *.test.* -------------------------------------------------------------------------------- /util/helpers/fetcher.ts: -------------------------------------------------------------------------------- 1 | export const fetcher = (url: string) => fetch(url).then((r) => r.json()) 2 | 3 | export const fetchWithBody = (url: string, body: T) => { 4 | return fetch(url, { 5 | method: 'POST', 6 | headers: { 7 | 'Content-Type': 'application/json', 8 | }, 9 | body: JSON.stringify(body), 10 | }).then((res) => res.json()) 11 | } 12 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://ratethelandlord.org/sitemap-0.xml 4 | https://ratethelandlord.org/sitemap.xml 5 | https://ratethelandlord.org/server-sitemap.xml 6 | -------------------------------------------------------------------------------- /lib/review/models/user-report-review.ts: -------------------------------------------------------------------------------- 1 | import sql from '@/lib/db' 2 | 3 | export async function report(id: number, reason: string): Promise { 4 | reason = reason.length > 250 ? `${reason.substring(0, 250)}...` : reason 5 | await sql`UPDATE review SET flagged = true, flagged_reason = ${reason} 6 | WHERE id = ${id} RETURNING id;` 7 | 8 | return id 9 | } 10 | -------------------------------------------------------------------------------- /migrations/003-users.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | await DB` 3 | ALTER TABLE users 4 | DROP COLUMN IF EXISTS password, 5 | DROP COLUMN IF EXISTS blocked, 6 | DROP COLUMN IF EXISTS login_attempts, 7 | DROP COLUMN IF EXISTS login_lockout, 8 | DROP COLUMN IF EXISTS last_login_attempt, 9 | DROP COLUMN IF EXISTS lockout_time; 10 | ` 11 | } 12 | -------------------------------------------------------------------------------- /util/helpers/filter-options.ts: -------------------------------------------------------------------------------- 1 | import { SortOptions } from '../interfaces/interfaces' 2 | 3 | export const sortOptions: SortOptions[] = [ 4 | { id: 1, name: 'name_az', value: 'az' }, 5 | { id: 2, name: 'name_za', value: 'za' }, 6 | { id: 3, name: 'newest', value: 'new' }, 7 | { id: 4, name: 'oldest', value: 'old' }, 8 | { id: 5, name: 'highest', value: 'high' }, 9 | { id: 6, name: 'lowest', value: 'low' }, 10 | ] 11 | -------------------------------------------------------------------------------- /messages/en-CA/layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": { 3 | "footer": { 4 | "copy": "Rate The Landlord Inc. All Rights Reserved." 5 | }, 6 | "nav": { 7 | "title": "Rate The Landlord", 8 | "reviews": "Reviews", 9 | "search": "Search", 10 | "submit": "Submit a Review", 11 | "open": "Open main menu", 12 | "about": "About", 13 | "resources": "Resources", 14 | "support-us": "Support Us" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /migrations/006-ca_location.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | const tableExists = await DB` 3 | SELECT EXISTS ( 4 | SELECT 1 5 | FROM information_schema.tables 6 | WHERE table_name = 'ca_location' 7 | )` 8 | if (!tableExists[0].exists) { 9 | await DB` 10 | CREATE TABLE ca_location ( 11 | zip TEXT PRIMARY KEY, 12 | latitude TEXT, 13 | longitude TEXT 14 | ); 15 | ` 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /migrations/007-us_location.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | const tableExists = await DB` 3 | SELECT EXISTS ( 4 | SELECT 1 5 | FROM information_schema.tables 6 | WHERE table_name = 'us_location' 7 | )` 8 | if (!tableExists[0].exists) { 9 | await DB` 10 | CREATE TABLE us_location ( 11 | zip TEXT PRIMARY KEY, 12 | latitude TEXT, 13 | longitude TEXT 14 | ); 15 | ` 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /components/reviews/components/ReviewHero.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslations } from 'next-intl' 2 | 3 | const ReviewHero = () => { 4 | const t = useTranslations('reviews') 5 | return ( 6 |
7 |

{t('hero_header')}

8 |
9 |

{t('hero_body')}

10 |
11 |
12 | ) 13 | } 14 | 15 | export default ReviewHero 16 | -------------------------------------------------------------------------------- /util/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | export const useDebounce = (value: string | undefined, delay: number) => { 4 | const [debouncedValue, setDebouncedValue] = useState(value) 5 | 6 | useEffect(() => { 7 | const timeoutId = setTimeout(() => { 8 | setDebouncedValue(value) 9 | }, delay) 10 | 11 | return () => clearTimeout(timeoutId) 12 | }, [value, delay]) 13 | 14 | return debouncedValue 15 | } 16 | -------------------------------------------------------------------------------- /migrations/014-review.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | await DB` 3 | ALTER TABLE review 4 | ADD COLUMN user_code TEXT DEFAULT NULL, 5 | ADD COLUMN last_user_attempt TIMESTAMP DEFAULT NULL, 6 | ADD COLUMN number_user_attempts numeric DEFAULT 0, 7 | ADD COLUMN number_user_edits numeric DEFAULT 0, 8 | ADD COLUMN has_user_code boolean GENERATED ALWAYS AS ( 9 | user_code IS NOT NULL 10 | ) STORED, 11 | ` 12 | } 13 | -------------------------------------------------------------------------------- /components/landlord/LandlordBanner.tsx: -------------------------------------------------------------------------------- 1 | import { SuspiciousLandlord } from '@/util/interfaces/interfaces' 2 | 3 | interface IProps { 4 | landlord: SuspiciousLandlord 5 | } 6 | 7 | const LandlordBanner = ({ landlord }: IProps) => { 8 | return ( 9 |
10 |
11 |

{landlord.message}

12 |
13 |
14 | ) 15 | } 16 | 17 | export default LandlordBanner 18 | -------------------------------------------------------------------------------- /messages/fr-CA/layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": { 3 | "footer": { 4 | "copy": "Rate The Landlord Inc. Tous droits réservés." 5 | }, 6 | "nav": { 7 | "title": "Rate The Landlord", 8 | "reviews": "Évaluations", 9 | "search": "Recherche", 10 | "submit": "Soumettre une évaluation", 11 | "open": "Ouvrir le menu principal", 12 | "about": "À propos", 13 | "resources": "Ressources", 14 | "support-us": "Nous soutenir" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pages/api/flagged-keywords/get-flagged-keywords.tsx: -------------------------------------------------------------------------------- 1 | import { getFlaggedKeywords } from '@/lib/flagged-keywords/flagged-keywords' 2 | import rateLimitMiddleware from '@/util/rateLimit' 3 | import { NextApiRequest, NextApiResponse } from 'next' 4 | 5 | const handler = async (req: NextApiRequest, res: NextApiResponse) => { 6 | const landlords = await getFlaggedKeywords() 7 | 8 | res.status(200).json(landlords) 9 | } 10 | 11 | export default rateLimitMiddleware(handler) 12 | -------------------------------------------------------------------------------- /components/admin/sections/TenantResources.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import { render } from '@/test-utils' 5 | import TenantResources from './TenantResources' 6 | import { axe } from 'jest-axe' 7 | 8 | describe('TenantResources', () => { 9 | it('Should not have a11y violation', async () => { 10 | const { container } = render() 11 | const result = await axe(container) 12 | expect(result).toHaveNoViolations() 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "semi": false, 4 | "arrowParens": "always", 5 | "bracketSpacing": true, 6 | "endOfLine": "lf", 7 | "htmlWhitespaceSensitivity": "css", 8 | "insertPragma": false, 9 | "jsxSingleQuote": true, 10 | "proseWrap": "always", 11 | "quoteProps": "as-needed", 12 | "requirePragma": false, 13 | "singleQuote": true, 14 | "tabWidth": 2, 15 | "trailingComma": "all", 16 | "useTabs": true, 17 | "plugins": ["prettier-plugin-tailwindcss"] 18 | } 19 | -------------------------------------------------------------------------------- /migrations/009-recent_reviews.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | const tableExists = await DB` 3 | SELECT EXISTS ( 4 | SELECT 1 5 | FROM information_schema.tables 6 | WHERE table_name = 'recent_review' 7 | )` 8 | if (!tableExists[0].exists) { 9 | await DB` 10 | CREATE TABLE recent_review ( 11 | id SERIAL PRIMARY KEY, 12 | landlord TEXT, 13 | created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP 14 | ); 15 | ` 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/review/models/admin-delete-review.ts: -------------------------------------------------------------------------------- 1 | import sql from '@/lib/db' 2 | import { Review } from '@/util/interfaces/interfaces' 3 | 4 | export async function getDeleted(): Promise { 5 | const reviews = await sql< 6 | Review[] 7 | >`SELECT * FROM review WHERE delete_date IS NOT NULL;` 8 | return reviews 9 | } 10 | 11 | export async function deleteReview(id: number): Promise { 12 | await sql`DELETE 13 | FROM review 14 | WHERE ID = ${id};` 15 | return true 16 | } 17 | -------------------------------------------------------------------------------- /lib/tenant-resource/models/resource.ts: -------------------------------------------------------------------------------- 1 | export interface Resource { 2 | id?: number 3 | name: string 4 | country_code: string 5 | city: string 6 | state: string 7 | address?: string 8 | phone_number?: string 9 | date_added?: Date // auto-generated by db 10 | description: string 11 | href: string 12 | } 13 | 14 | export interface ResourcesResponse { 15 | resources: Resource[] 16 | total: number 17 | countries: string[] 18 | states: string[] 19 | cities: string[] 20 | limit: string 21 | } 22 | -------------------------------------------------------------------------------- /pages/api/review/state-info.tsx: -------------------------------------------------------------------------------- 1 | import { getStateStats } from '@/lib/review/state-stats' 2 | import { runMiddleware } from '@/util/cors' 3 | import { NextApiRequest, NextApiResponse } from 'next' 4 | 5 | const getStats = async (req: NextApiRequest, res: NextApiResponse) => { 6 | await runMiddleware(req, res) 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument 9 | const stats = await getStateStats(req.body) 10 | res.status(200).json(stats) 11 | } 12 | 13 | export default getStats 14 | -------------------------------------------------------------------------------- /migrations/010-spam_landlord.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | const tableExists = await DB` 3 | SELECT EXISTS ( 4 | SELECT 1 5 | FROM information_schema.tables 6 | WHERE table_name = 'spam_landlords' 7 | )` 8 | if (!tableExists[0].exists) { 9 | await DB` 10 | CREATE TABLE spam_landlords ( 11 | id SERIAL PRIMARY KEY, 12 | landlord TEXT, 13 | message TEXT, 14 | created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP 15 | ); 16 | ` 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /migrations/011-keyword_flagging.ts: -------------------------------------------------------------------------------- 1 | exports.up = async function (DB) { 2 | const tableExists = await DB` 3 | SELECT EXISTS ( 4 | SELECT 1 5 | FROM information_schema.tables 6 | WHERE table_name = 'keyword_flags' 7 | )` 8 | if (!tableExists[0].exists) { 9 | await DB` 10 | CREATE TABLE keyword_flags ( 11 | id SERIAL PRIMARY KEY, 12 | keyword TEXT, 13 | reason TEXT, 14 | created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP 15 | ); 16 | ` 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pages/api/review/get-landlords.tsx: -------------------------------------------------------------------------------- 1 | import { getLandlords } from '@/lib/review/landlords' 2 | import { runMiddleware } from '@/util/cors' 3 | import rateLimitMiddleware from '@/util/rateLimit' 4 | import { NextApiRequest, NextApiResponse } from 'next' 5 | 6 | const handler = async (req: NextApiRequest, res: NextApiResponse) => { 7 | await runMiddleware(req, res) 8 | 9 | const landlords = await getLandlords() 10 | 11 | res.status(200).json(landlords) 12 | } 13 | 14 | export default rateLimitMiddleware(handler) 15 | -------------------------------------------------------------------------------- /components/privacy/Privacy.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import React from 'react' 5 | import { render } from '@/test-utils' 6 | import Privacy from './Privacy' 7 | import { axe, toHaveNoViolations } from 'jest-axe' 8 | expect.extend(toHaveNoViolations) 9 | 10 | describe('Privacy Component', () => { 11 | it('Should not have a11y violation', async () => { 12 | const { container } = render() 13 | const result = await axe(container) 14 | expect(result).toHaveNoViolations() 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /pages/api/review/state-city-info.tsx: -------------------------------------------------------------------------------- 1 | import { getTopCitiesStats } from '@/lib/review/state-stats' 2 | import { runMiddleware } from '@/util/cors' 3 | import { NextApiRequest, NextApiResponse } from 'next' 4 | 5 | const getTopCityStats = async (req: NextApiRequest, res: NextApiResponse) => { 6 | await runMiddleware(req, res) 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument 9 | const stats = await getTopCitiesStats(req.body) 10 | res.status(200).json(stats) 11 | } 12 | 13 | export default getTopCityStats 14 | -------------------------------------------------------------------------------- /pages/api/suspicious-landlords/get-landlords.tsx: -------------------------------------------------------------------------------- 1 | import { getSuspiciousLandlords } from '@/lib/suspicious-landlords/suspicious-landlords' 2 | import rateLimitMiddleware from '@/util/rateLimit' 3 | import { NextApiRequest, NextApiResponse } from 'next' 4 | 5 | const getSuspiciousLandlordsAPI = async ( 6 | req: NextApiRequest, 7 | res: NextApiResponse, 8 | ) => { 9 | const landlords = await getSuspiciousLandlords() 10 | 11 | res.status(200).json(landlords) 12 | } 13 | 14 | export default rateLimitMiddleware(getSuspiciousLandlordsAPI) 15 | -------------------------------------------------------------------------------- /util/hooks/useScreenWidth.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | 3 | const useScreenWidth = () => { 4 | const [screenWidth, setScreenWidth] = useState(0) 5 | 6 | useEffect(() => { 7 | const handleResize = () => setScreenWidth(window.innerWidth) 8 | 9 | handleResize() // Set initial width 10 | window.addEventListener('resize', handleResize) 11 | 12 | return () => window.removeEventListener('resize', handleResize) 13 | }, []) 14 | 15 | return screenWidth 16 | } 17 | 18 | export default useScreenWidth 19 | -------------------------------------------------------------------------------- /components/admin/sections/SuspiciousLandlords.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | import { render } from '@/test-utils' 6 | import '@testing-library/jest-dom' 7 | import SuspiciousLandlords from './SuspiciousLandlords' 8 | import { axe } from 'jest-axe' 9 | 10 | describe('SuspiciousLandlords', () => { 11 | it('Should not have a11y violation', async () => { 12 | const { container } = render() 13 | const result = await axe(container) 14 | expect(result).toHaveNoViolations() 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /components/city/CitiesTable.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | import { render } from '@/test-utils' 6 | import { axe, toHaveNoViolations } from 'jest-axe' 7 | expect.extend(toHaveNoViolations) 8 | 9 | import CitiesTable from './CitiesTable' 10 | 11 | describe('CitiesTable Component', () => { 12 | it('Should not have a11y violation', async () => { 13 | const { container } = render() 14 | const result = await axe(container) 15 | expect(result).toHaveNoViolations() 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /components/layout/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Footer from './footer' 3 | import Navbar from './navbar' 4 | import Banner from '../PostHog/CookieBanner' 5 | 6 | function Layout({ children }: { children: JSX.Element }): JSX.Element { 7 | return ( 8 | <> 9 | 10 |
14 | {children} 15 |
16 | 17 |